lex-telemetry 0.1.6 → 0.1.8

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: f468345bff8197f58b2ef7736b8c03e2ede0e6521505bbd4c8b73e6f9c606a89
4
- data.tar.gz: e4e9783b090af3812310e385e51c5a5950b57868faa7c87863d77a8ad7a6b1e2
3
+ metadata.gz: 38dd588b667de5c28ca831f41b9bb3e59a2df3ddb1da73760032b64bb868ada6
4
+ data.tar.gz: 16d9198d26d37a76fbdce09918dc46300a3322927fceb5d767b291f1b44657ce
5
5
  SHA512:
6
- metadata.gz: 0fac05554a0955707202e43c131dcfa1d54339e0750ff3f6640e5805cd6e92a60a3a71672f87b4415bd5d16de3e8ac4de3b8f005e754a01a570ea6c2a39eabc2
7
- data.tar.gz: 4f5e12b043042b264e257f8cf99747aa020376083bbd9c727429e4cc702788f2f4b1a69a9d13962febc5fbc4cd22be00f893050609125b598588e1947e71951f
6
+ metadata.gz: da852fb40d7273f9f4f7074c1cc94dcaca5411511d1b183df78d327cd290d312aee0109d708d8e5227749383bbae0c01a37022b508ecd518cdbd0721bb755858
7
+ data.tar.gz: 7fcb82807a28ecabb01d95fa0f227067ab1ee50679386132914efae509fa6449846b87f2c70fa83b31b793cb9a56f3536d273c3704230cf86d352675bf87da7c
@@ -10,8 +10,8 @@ jobs:
10
10
  ci:
11
11
  uses: LegionIO/.github/.github/workflows/ci.yml@main
12
12
 
13
- lint:
14
- uses: LegionIO/.github/.github/workflows/lint-patterns.yml@main
13
+ excluded-files:
14
+ uses: LegionIO/.github/.github/workflows/excluded-files.yml@main
15
15
 
16
16
  security:
17
17
  uses: LegionIO/.github/.github/workflows/security-scan.yml@main
@@ -27,8 +27,8 @@ jobs:
27
27
  uses: LegionIO/.github/.github/workflows/stale.yml@main
28
28
 
29
29
  release:
30
- needs: [ci, lint]
30
+ needs: [ci, excluded-files]
31
31
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
32
32
  uses: LegionIO/.github/.github/workflows/release.yml@main
33
33
  secrets:
34
- rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
34
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.rubocop.yml CHANGED
@@ -1,67 +1,18 @@
1
- AllCops:
2
- TargetRubyVersion: 3.4
3
- NewCops: enable
4
- SuggestExtensions: false
1
+ inherit_gem:
2
+ rubocop-legion: config/lex.yml
5
3
 
6
- Layout/LineLength:
7
- Max: 160
8
-
9
- Layout/SpaceAroundEqualsInParameterDefault:
10
- EnforcedStyle: space
11
-
12
- Layout/HashAlignment:
13
- EnforcedHashRocketStyle: table
14
- EnforcedColonStyle: table
15
-
16
- Metrics/MethodLength:
17
- Max: 50
18
-
19
- Metrics/ClassLength:
20
- Max: 1500
21
-
22
- Metrics/ModuleLength:
23
- Max: 1500
24
-
25
- Metrics/BlockLength:
26
- Max: 40
27
- Exclude:
28
- - 'spec/**/*'
29
-
30
- Metrics/AbcSize:
31
- Max: 60
32
-
33
- Metrics/CyclomaticComplexity:
34
- Max: 15
35
-
36
- Metrics/PerceivedComplexity:
37
- Max: 17
38
-
39
- Style/Documentation:
40
- Enabled: false
41
-
42
- Style/SymbolArray:
43
- Enabled: true
44
-
45
- Style/FrozenStringLiteralComment:
46
- Enabled: true
47
- EnforcedStyle: always
48
-
49
- Naming/FileName:
4
+ # Actors define `def time` as instance method — cop looks for DSL call, false positive
5
+ Legion/Extension/EveryActorRequiresTime:
50
6
  Enabled: false
51
7
 
52
- Naming/PredicateMethod:
8
+ # Runner module is designed for include into actors; not standalone
9
+ Legion/Extension/RunnerIncludeHelpers:
53
10
  Enabled: false
54
11
 
55
- Gemspec/DevelopmentDependencies:
12
+ # Class instance variables use Concurrent::Hash/AtomicFixnum — thread-safe by design
13
+ ThreadSafety/ClassInstanceVariable:
56
14
  Enabled: false
57
15
 
16
+ # TelemetryEvent#build legitimately requires many named parameters for type safety
58
17
  Metrics/ParameterLists:
59
18
  Enabled: false
60
-
61
- Lint/EmptyClass:
62
- Exclude:
63
- - 'spec/**/*'
64
-
65
- Style/MultilineIfModifier:
66
- Exclude:
67
- - 'spec/**/*'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.8] - 2026-03-31
4
+
5
+ ### Added
6
+ - `StatsReporter` actor (Every 60s): periodically calls `system_stats` to collect and ship aggregate subsystem metrics
7
+ - `system_stats` runner method: collects stats from Transport, Cache, Data, LLM, Extensions, and GAIA subsystems; publishes via telemetry exchange
8
+ - `Helpers::SubsystemStats`: per-subsystem collectors with graceful guards (returns nil when subsystem not loaded)
9
+
10
+ ## [0.1.7] - 2026-03-30
11
+
12
+ ### Changed
13
+ - update to rubocop-legion 0.1.7, resolve all offenses
14
+
3
15
  ## [0.1.6] - 2026-03-26
4
16
 
5
17
  ### Changed
data/Gemfile CHANGED
@@ -4,3 +4,4 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'concurrent-ruby'
7
+ gem 'rubocop-legion', '~> 0.1'
@@ -37,7 +37,6 @@ Gem::Specification.new do |spec|
37
37
  spec.add_dependency 'legion-settings', '>= 1.3.14'
38
38
  spec.add_dependency 'legion-transport', '>= 1.3.9'
39
39
 
40
- spec.add_development_dependency 'rake'
41
40
  spec.add_development_dependency 'rspec'
42
41
  spec.add_development_dependency 'rubocop'
43
42
  spec.add_development_dependency 'rubocop-rspec'
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Telemetry
6
+ module Actor
7
+ class StatsReporter < Legion::Extensions::Actors::Every
8
+ def runner_class
9
+ 'Legion::Extensions::Telemetry::Runners::Telemetry'
10
+ end
11
+
12
+ def runner_function
13
+ 'system_stats'
14
+ end
15
+
16
+ def time
17
+ 60
18
+ end
19
+
20
+ def run_now?
21
+ false
22
+ end
23
+
24
+ def use_runner?
25
+ false
26
+ end
27
+
28
+ def check_subtask?
29
+ false
30
+ end
31
+
32
+ def generate_task?
33
+ false
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Telemetry
6
+ module Helpers
7
+ module SubsystemStats
8
+ module_function
9
+
10
+ def collect_transport
11
+ return nil unless Legion.const_defined?(:Transport, false)
12
+
13
+ stats = {}
14
+
15
+ if Legion::Transport.respond_to?(:connection) && Legion::Transport.connection.respond_to?(:status)
16
+ stats[:connection_status] = Legion::Transport.connection.status.to_s
17
+ end
18
+
19
+ if Legion::Transport.respond_to?(:connection) && Legion::Transport.connection.respond_to?(:channel_max)
20
+ stats[:channel_max] = Legion::Transport.connection.channel_max
21
+ end
22
+
23
+ stats
24
+ rescue StandardError => _e
25
+ nil
26
+ end
27
+
28
+ def collect_cache
29
+ return nil unless defined?(Legion::Cache)
30
+
31
+ stats = {}
32
+
33
+ if Legion::Cache.respond_to?(:stats)
34
+ raw = Legion::Cache.stats
35
+ stats.merge!(raw) if raw.is_a?(Hash)
36
+ end
37
+
38
+ stats
39
+ rescue StandardError => _e
40
+ nil
41
+ end
42
+
43
+ def collect_data
44
+ return nil unless defined?(Legion::Data)
45
+
46
+ stats = {}
47
+
48
+ stats[:connected] = Legion::Data.connected? if Legion::Data.respond_to?(:connected?)
49
+
50
+ stats[:pool_size] = Legion::Data::Pool.size if defined?(Legion::Data::Pool) && Legion::Data::Pool.respond_to?(:size)
51
+
52
+ stats
53
+ rescue StandardError => _e
54
+ nil
55
+ end
56
+
57
+ def collect_llm
58
+ return nil unless defined?(Legion::LLM)
59
+
60
+ stats = {}
61
+
62
+ if Legion::LLM.respond_to?(:pipeline_stats)
63
+ raw = Legion::LLM.pipeline_stats
64
+ stats.merge!(raw) if raw.is_a?(Hash)
65
+ end
66
+
67
+ stats
68
+ rescue StandardError => _e
69
+ nil
70
+ end
71
+
72
+ def collect_extensions
73
+ return nil unless defined?(Legion::Extensions)
74
+
75
+ stats = {}
76
+
77
+ if Legion::Extensions.respond_to?(:loaded)
78
+ loaded = Legion::Extensions.loaded
79
+ stats[:loaded_count] = loaded.is_a?(Array) ? loaded.length : loaded.to_i
80
+ end
81
+
82
+ stats
83
+ rescue StandardError => _e
84
+ nil
85
+ end
86
+
87
+ def collect_gaia
88
+ return nil unless defined?(Legion::Gaia)
89
+
90
+ stats = {}
91
+
92
+ stats[:tick_count] = Legion::Gaia.tick_count if Legion::Gaia.respond_to?(:tick_count)
93
+
94
+ stats[:active_phase] = Legion::Gaia.active_phase.to_s if Legion::Gaia.respond_to?(:active_phase)
95
+
96
+ stats
97
+ rescue StandardError => _e
98
+ nil
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -18,11 +18,11 @@ module Legion
18
18
  first_line = File.open(path, &:readline)
19
19
  data = ::JSON.parse(first_line)
20
20
  data.is_a?(Hash) && (data.key?('sessionId') || data.key?('parentUuid'))
21
- rescue StandardError
21
+ rescue StandardError => _e
22
22
  false
23
23
  end
24
24
 
25
- def parse(path, offset: 0, &block)
25
+ def parse(path, offset: 0, &)
26
26
  pending_tools = {}
27
27
  session_id = nil
28
28
 
@@ -36,13 +36,13 @@ module Legion
36
36
 
37
37
  if first_line && offset.zero?
38
38
  session_id = data['sessionId'] || data['uuid'] || SecureRandom.uuid
39
- block.call(Helpers::TelemetryEvent.build(
40
- event_type: :session_start,
41
- session_id: session_id,
42
- source: source_name,
43
- timestamp: timestamp,
44
- metadata: { version: data['version'] }
45
- ))
39
+ yield(Helpers::TelemetryEvent.build(
40
+ event_type: :session_start,
41
+ session_id: session_id,
42
+ source: source_name,
43
+ timestamp: timestamp,
44
+ metadata: { version: data['version'] }
45
+ ))
46
46
  first_line = false
47
47
  next
48
48
  end
@@ -51,9 +51,9 @@ module Legion
51
51
  msg = data['message']
52
52
  next unless msg.is_a?(Hash)
53
53
 
54
- process_assistant(msg, session_id, timestamp, pending_tools, &block) if msg['role'] == 'assistant'
55
- process_tool_result(msg, session_id, timestamp, pending_tools, &block) if msg['role'] == 'user'
56
- rescue ::JSON::ParserError
54
+ process_assistant(msg, session_id, timestamp, pending_tools, &) if msg['role'] == 'assistant'
55
+ process_tool_result(msg, session_id, timestamp, pending_tools, &) if msg['role'] == 'user'
56
+ rescue ::JSON::ParserError => _e
57
57
  next
58
58
  end
59
59
 
@@ -63,7 +63,7 @@ module Legion
63
63
 
64
64
  private
65
65
 
66
- def process_assistant(msg, session_id, timestamp, pending_tools, &block)
66
+ def process_assistant(msg, session_id, timestamp, pending_tools)
67
67
  content = msg['content']
68
68
  usage = msg['usage']
69
69
 
@@ -85,16 +85,16 @@ module Legion
85
85
  has_tool_use = content.is_a?(Array) && content.any? { |b| b['type'] == 'tool_use' }
86
86
  return if has_tool_use
87
87
 
88
- block.call(Helpers::TelemetryEvent.build(
89
- event_type: :llm_request,
90
- session_id: session_id,
91
- source: source_name,
92
- timestamp: timestamp,
93
- tokens: extract_tokens(usage)
94
- ))
88
+ yield(Helpers::TelemetryEvent.build(
89
+ event_type: :llm_request,
90
+ session_id: session_id,
91
+ source: source_name,
92
+ timestamp: timestamp,
93
+ tokens: extract_tokens(usage)
94
+ ))
95
95
  end
96
96
 
97
- def process_tool_result(msg, session_id, timestamp, pending_tools, &block)
97
+ def process_tool_result(msg, session_id, timestamp, pending_tools)
98
98
  content = msg['content']
99
99
  return unless content.is_a?(Array)
100
100
 
@@ -106,16 +106,16 @@ module Legion
106
106
 
107
107
  duration_ms = ((timestamp - pending[:timestamp]) * 1000).to_i if timestamp && pending[:timestamp]
108
108
 
109
- block.call(Helpers::TelemetryEvent.build(
110
- event_type: :tool_call,
111
- session_id: session_id,
112
- source: source_name,
113
- timestamp: pending[:timestamp],
114
- tool_name: pending[:tool_name],
115
- tool_input: pending[:tool_input],
116
- duration_ms: duration_ms,
117
- tokens: pending[:tokens]
118
- ))
109
+ yield(Helpers::TelemetryEvent.build(
110
+ event_type: :tool_call,
111
+ session_id: session_id,
112
+ source: source_name,
113
+ timestamp: pending[:timestamp],
114
+ tool_name: pending[:tool_name],
115
+ tool_input: pending[:tool_input],
116
+ duration_ms: duration_ms,
117
+ tokens: pending[:tokens]
118
+ ))
119
119
  end
120
120
  end
121
121
 
@@ -134,7 +134,7 @@ module Legion
134
134
  return nil unless str
135
135
 
136
136
  Time.parse(str)
137
- rescue ArgumentError
137
+ rescue ArgumentError => _e
138
138
  nil
139
139
  end
140
140
 
@@ -94,7 +94,7 @@ module Legion
94
94
  def publish_pending(**_opts)
95
95
  if privacy_mode?
96
96
  count = event_store.pending.length
97
- Legion::Logging.info "lex-telemetry: privacy mode active, suppressing #{count} pending events (local logging only)" if defined?(Legion::Logging)
97
+ Legion::Logging.info "lex-telemetry: privacy mode active, suppressing #{count} pending events (local logging only)" # rubocop:disable Legion/HelperMigration/DirectLogging
98
98
  event_store.pending.clear
99
99
  return { success: true, published: 0, suppressed: true, reason: 'enterprise_data_privacy is enabled' }
100
100
  end
@@ -109,7 +109,7 @@ module Legion
109
109
  Transport::Messages::TelemetryMessage.new.publish(event, routing_key: routing_key)
110
110
  published += 1
111
111
  end
112
- rescue StandardError
112
+ rescue StandardError => _e
113
113
  event_store.pending.push(event)
114
114
  end
115
115
 
@@ -149,7 +149,7 @@ module Legion
149
149
  high_water_mark.set(path: path, offset: new_offset)
150
150
  files_processed += 1
151
151
  events_ingested += count
152
- rescue StandardError
152
+ rescue StandardError => _e
153
153
  next
154
154
  end
155
155
  end
@@ -174,7 +174,7 @@ module Legion
174
174
  affinity = if defined?(Legion::Settings)
175
175
  begin
176
176
  Legion::Settings.dig(:region, :default_affinity)
177
- rescue StandardError
177
+ rescue StandardError => _e
178
178
  'prefer_local'
179
179
  end
180
180
  else
@@ -202,6 +202,31 @@ module Legion
202
202
  end
203
203
  end
204
204
 
205
+ def system_stats(**_opts)
206
+ stats = {
207
+ transport: Helpers::SubsystemStats.collect_transport,
208
+ cache: Helpers::SubsystemStats.collect_cache,
209
+ data: Helpers::SubsystemStats.collect_data,
210
+ llm: Helpers::SubsystemStats.collect_llm,
211
+ extensions: Helpers::SubsystemStats.collect_extensions,
212
+ gaia: Helpers::SubsystemStats.collect_gaia,
213
+ timestamp: Time.now.to_i
214
+ }.compact
215
+
216
+ begin
217
+ if defined?(Legion::Extensions::Telemetry::Transport::Messages::TelemetryMessage)
218
+ payload = { event_type: :system_stats, source: :system, data: stats }
219
+ Transport::Messages::TelemetryMessage.new.publish(payload)
220
+ end
221
+ rescue StandardError => _e
222
+ nil
223
+ end
224
+
225
+ { success: true, stats: stats }
226
+ rescue StandardError => e
227
+ { success: false, error: e.message }
228
+ end
229
+
205
230
  def reset!
206
231
  @event_store = nil
207
232
  @parsers = nil
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Telemetry
6
- VERSION = '0.1.6'
6
+ VERSION = '0.1.8'
7
7
  end
8
8
  end
9
9
  end
@@ -8,12 +8,13 @@ require 'legion/extensions/telemetry/parsers/claude_code'
8
8
  require 'legion/extensions/telemetry/helpers/event_store'
9
9
  require 'legion/extensions/telemetry/helpers/stats'
10
10
  require 'legion/extensions/telemetry/helpers/high_water_mark'
11
+ require 'legion/extensions/telemetry/helpers/subsystem_stats'
11
12
  require 'legion/extensions/telemetry/runners/telemetry'
12
13
 
13
14
  module Legion
14
15
  module Extensions
15
16
  module Telemetry
16
- extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
17
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
17
18
 
18
19
  def self.remote_invocable?
19
20
  false
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-telemetry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -121,20 +121,6 @@ dependencies:
121
121
  - - ">="
122
122
  - !ruby/object:Gem::Version
123
123
  version: 1.3.9
124
- - !ruby/object:Gem::Dependency
125
- name: rake
126
- requirement: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - ">="
129
- - !ruby/object:Gem::Version
130
- version: '0'
131
- type: :development
132
- prerelease: false
133
- version_requirements: !ruby/object:Gem::Requirement
134
- requirements:
135
- - - ">="
136
- - !ruby/object:Gem::Version
137
- version: '0'
138
124
  - !ruby/object:Gem::Dependency
139
125
  name: rspec
140
126
  requirement: !ruby/object:Gem::Requirement
@@ -215,10 +201,12 @@ files:
215
201
  - lib/legion/extensions/telemetry/actors/collector.rb
216
202
  - lib/legion/extensions/telemetry/actors/publisher.rb
217
203
  - lib/legion/extensions/telemetry/actors/region_reporter.rb
204
+ - lib/legion/extensions/telemetry/actors/stats_reporter.rb
218
205
  - lib/legion/extensions/telemetry/helpers/event_store.rb
219
206
  - lib/legion/extensions/telemetry/helpers/high_water_mark.rb
220
207
  - lib/legion/extensions/telemetry/helpers/scrubber.rb
221
208
  - lib/legion/extensions/telemetry/helpers/stats.rb
209
+ - lib/legion/extensions/telemetry/helpers/subsystem_stats.rb
222
210
  - lib/legion/extensions/telemetry/helpers/telemetry_event.rb
223
211
  - lib/legion/extensions/telemetry/parsers/base.rb
224
212
  - lib/legion/extensions/telemetry/parsers/claude_code.rb