Module: Legion::MCP::StateTracker

Extended by:
Logging::Helper
Defined in:
lib/legion/mcp/state_tracker.rb

Constant Summary collapse

MAX_SNAPSHOTS =
50

Class Method Summary collapse

Class Method Details

.collect_extension_countObject



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/legion/mcp/state_tracker.rb', line 70

def collect_extension_count
  return 0 unless defined?(Legion::Extensions)

  extensions = if Legion::Extensions.respond_to?(:extensions)
                 Legion::Extensions.extensions
               else
                 Legion::Extensions.instance_variable_get(:@extensions)
               end
  extensions&.size || 0
rescue StandardError => e
  handle_exception(e, level: :debug, operation: 'legion.mcp.state_tracker.collect_extension_count')
  0
end

.collect_observer_statsObject



51
52
53
54
55
56
57
58
59
# File 'lib/legion/mcp/state_tracker.rb', line 51

def collect_observer_stats
  return {} unless defined?(Observer)

  stats = Observer.stats
  { total_calls: stats[:total_calls], tool_count: stats[:tool_count], failure_rate: stats[:failure_rate] }
rescue StandardError => e
  handle_exception(e, level: :debug, operation: 'legion.mcp.state_tracker.collect_observer_stats')
  {}
end

.collect_pattern_countObject



61
62
63
64
65
66
67
68
# File 'lib/legion/mcp/state_tracker.rb', line 61

def collect_pattern_count
  return 0 unless defined?(PatternStore)

  PatternStore.respond_to?(:size) ? PatternStore.size : 0
rescue StandardError => e
  handle_exception(e, level: :debug, operation: 'legion.mcp.state_tracker.collect_pattern_count')
  0
end

.collect_stateObject



42
43
44
45
46
47
48
49
# File 'lib/legion/mcp/state_tracker.rb', line 42

def collect_state
  {
    tool_count:     Server.tool_registry.size,
    observer_stats: collect_observer_stats,
    pattern_count:  collect_pattern_count,
    extensions:     collect_extension_count
  }
end

.compute_diff(baseline, current) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/legion/mcp/state_tracker.rb', line 84

def compute_diff(baseline, current)
  changes = {}
  all_keys = (baseline.keys | current.keys).uniq

  all_keys.each do |key|
    old_val = baseline[key]
    new_val = current[key]

    next if old_val == new_val

    if old_val.is_a?(Hash) && new_val.is_a?(Hash)
      nested = compute_diff(old_val, new_val)
      changes[key] = nested unless nested.empty?
    else
      changes[key] = { before: old_val, after: new_val }
    end
  end

  changes
end

.diff(since:) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/legion/mcp/state_tracker.rb', line 25

def diff(since:)
  log.info('Starting legion.mcp.state_tracker.diff')
  since_time = parse_time(since)
  return { error: 'invalid timestamp' } unless since_time

  baseline = find_baseline(since_time)
  current = collect_state

  if baseline.nil?
    return { full_state: current, reason: 'no baseline found for given timestamp',
             timestamp: Time.now.iso8601 }
  end

  changes = compute_diff(baseline[:state], current)
  { changes: changes, since: since_time.iso8601, timestamp: Time.now.iso8601 }
end

.find_baseline(since_time) ⇒ Object



105
106
107
108
109
# File 'lib/legion/mcp/state_tracker.rb', line 105

def find_baseline(since_time)
  snapshots_mutex.synchronize do
    snapshots.reverse.find { |s| s[:timestamp] <= since_time }
  end
end

.parse_time(value) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/legion/mcp/state_tracker.rb', line 111

def parse_time(value)
  case value
  when Time
    value
  when String
    Time.parse(value)
  when Numeric
    Time.at(value)
  end
rescue ArgumentError => e
  handle_exception(e, level: :debug, operation: 'legion.mcp.state_tracker.parse_time')
  nil
end

.reset!Object



133
134
135
# File 'lib/legion/mcp/state_tracker.rb', line 133

def reset!
  snapshots_mutex.synchronize { snapshots.clear }
end

.snapshotObject



12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/legion/mcp/state_tracker.rb', line 12

def snapshot
  log.info('Starting legion.mcp.state_tracker.snapshot')
  state = collect_state
  timestamp = Time.now.floor

  snapshots_mutex.synchronize do
    snapshots << { state: state, timestamp: timestamp }
    snapshots.shift if snapshots.size > MAX_SNAPSHOTS
  end

  { state: state, timestamp: timestamp.iso8601 }
end

.snapshotsObject



125
126
127
# File 'lib/legion/mcp/state_tracker.rb', line 125

def snapshots
  @snapshots ||= []
end

.snapshots_mutexObject



129
130
131
# File 'lib/legion/mcp/state_tracker.rb', line 129

def snapshots_mutex
  @snapshots_mutex ||= Mutex.new
end