legion-gaia 0.9.38 → 0.9.44

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: cc703551ee34547621861ff669735ce602151be08dcddcfdf0d704aebcd1bb7f
4
- data.tar.gz: 969f9fcf9510ec29b3eee6d0dc1d691d0836528367dbb352074ec0a9b1b9b423
3
+ metadata.gz: 162def8c335c98ed023f01a4a3a091cc9e6e43a4f965827b2b76b25ea09bbace
4
+ data.tar.gz: cb877734f54f0b877d74489071f46d06b108ebbb555e754fb920f901de27d5c4
5
5
  SHA512:
6
- metadata.gz: c134c1b4d190ec7dd315e2c6a734254a3002f60b104f1d6f291cfecab3125bc9cc38d62b5937e10e1e06b0e0313df3a3031b47a640eee363cc30e9a5db06947d
7
- data.tar.gz: f46a86969d2d6c16a5b204b08929cb3ddf86b53ca27a42414c1c657487163a0cc0cbdbb7d6f90368402a8b0be09eb9595ee54d3e1b3192bcc81e065897633fd4
6
+ metadata.gz: aa7c57507b415ff8ad3b7c1136c4c4180bc760082c21850606af1bb3910bea3422bad9af239b4c7a7ccda0b644e3cd6bfc8ea0a948020508786c71bbe572470a
7
+ data.tar.gz: 5d357170e157577b45eb834496ebc5822c290a063fb5eb6bc235238fbeec38b4609a58268d123478c356cbf7a8865216406f12f456cc244a447959b1fab4ab66
data/CHANGELOG.md CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.44] - 2026-04-02
6
+
7
+ ### Fixed
8
+ - Route Teams webhook traffic through `Legion::Gaia.ingest` so webhook-delivered frames use normal signal normalization, session tracking, and interlocutor observation instead of pushing raw frames into the sensory buffer
9
+
10
+ ## [0.9.43] - 2026-04-02
11
+
12
+ ### Fixed
13
+ - Preserve undelivered proactive intents when pending dispatch stops mid-drain, prevent failed sends from consuming proactive quota, and fail partner-directed dispatches when no partner channel can be resolved
14
+
15
+ ## [0.9.42] - 2026-04-02
16
+
17
+ ### Fixed
18
+ - Keep trackers dirty when Apollo persistence returns a failed upsert response, and avoid advancing the flush timestamp for unsuccessful tracker flushes
19
+ - Refresh repo docs to match the current GAIA version and the 25-phase wiring layout
20
+
21
+ ## [0.9.41] - 2026-04-02
22
+
23
+ ### Fixed
24
+ - Normalize `RouterBridge#route_outbound` delivery results so adapter-reported error hashes remain undelivered instead of being wrapped as successful outbound delivery
25
+
26
+ ## [0.9.40] - 2026-04-02
27
+
28
+ ### Fixed
29
+ - Preserve actual proactive delivery outcomes: `send_message`, `send_to_user`, and `start_conversation` now return failure when adapter or registry delivery reports failure instead of always reporting success
30
+ - Include explicit per-channel `:no_adapter` failures in `send_notification` fanout results instead of silently skipping requested channels
31
+ - Propagate adapter delivery errors through `ChannelRegistry#deliver` so upstream callers can report real delivery outcomes
32
+
33
+ ## [0.9.39] - 2026-04-02
34
+
35
+ ### Added
36
+ - Partner-absence signal queuing: queues an ambient `InputFrame` when `partner_absence_misses` exceeds `ABSENCE_SIGNAL_THRESHOLD` (5), with a 30-minute cooldown (`ABSENCE_SIGNAL_COOLDOWN`) and salience 0.75 to prevent signal flood
37
+
38
+ ### Changed
39
+ - Switch non-API GAIA library logging to `Legion::Logging::Helper`, replacing direct `Legion::Logging.*` calls and legacy `log_*` wrappers with helper-backed `log`
40
+ - Expand `info`, `debug`, and `error` coverage across GAIA runtime booting, routing, proactive delivery, trackers, adapters, and workflow transitions
41
+ - `PhaseWiring` `memory_retrieval` and `prediction_engine` now return `{ skip: true, reason: :idle_no_signals }` when the signals array is empty, skipping expensive phase execution on idle ticks
42
+ - `PhaseWiring` `knowledge_retrieval` now reads `signal[:value]` before falling back to `signal[:content]` for the query text
43
+ - `PhaseWiring` result normalization: `partner_observations` extracted via `partner_observations_from(ctx)` helper instead of direct `ctx.dig` to normalize observation shape
44
+
45
+ ### Fixed
46
+ - Route rescued GAIA library exceptions through `handle_exception` so failures are captured consistently with operation context
47
+ - Update GAIA logging specs to support helper-backed tagged logging and keep the full suite green after the logging uplift
48
+
5
49
  ## [0.9.38] - 2026-04-01
6
50
 
7
51
  ### Fixed
data/CLAUDE.md CHANGED
@@ -3,7 +3,7 @@
3
3
  **Repository Level 3 Documentation**
4
4
  - **Parent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
5
5
  - **GitHub**: https://github.com/LegionIO/legion-gaia
6
- - **Version**: 0.9.26
6
+ - **Version**: 0.9.44
7
7
 
8
8
  ## Purpose
9
9
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Cognitive coordination layer for the LegionIO framework. GAIA is the mind that inhabits the Legion body.
4
4
 
5
- **Version**: 0.9.26
5
+ **Version**: 0.9.44
6
6
 
7
7
  GAIA sits on top of LegionIO's infrastructure and coordinates all agentic subordinate functions. It drives the tick cycle, manages extension discovery and wiring, and provides the channel abstraction for multi-interface communication.
8
8
 
@@ -23,7 +23,7 @@ Legion::Gaia.ingest OutputRouter -> NotificationGate -> Ren
23
23
  v |
24
24
  SensoryBuffer -----> Heartbeat (1s) --> Cognitive Pipeline
25
25
  |
26
- PhaseWiring (24 phases)
26
+ PhaseWiring (25 phases)
27
27
  |
28
28
  Registry -> RunnerHost(s)
29
29
  |
@@ -102,11 +102,11 @@ gaia:
102
102
 
103
103
  ## Cognitive Phases
104
104
 
105
- GAIA wires 24 phases across two cycles:
105
+ GAIA wires 25 phases across two cycles:
106
106
 
107
107
  **Active Tick (16 phases):** sensory processing, emotional evaluation, memory retrieval, knowledge retrieval, identity entropy check, working memory integration, procedural check, prediction engine, mesh interface, social cognition, theory of mind, gut instinct, action selection, memory consolidation, homeostasis regulation, post-tick reflection.
108
108
 
109
- **Dream Cycle (8 phases):** memory audit, association walk, contradiction resolution, agenda formation, consolidation commit, knowledge promotion, dream reflection, dream narration.
109
+ **Dream Cycle (9 phases):** memory audit, association walk, contradiction resolution, agenda formation, consolidation commit, knowledge promotion, dream reflection, partner reflection, dream narration.
110
110
 
111
111
  ## Channel Adapters
112
112
 
data/legion-gaia.gemspec CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.add_dependency 'base64'
30
30
  spec.add_dependency 'legion-json', '>= 1.2.0'
31
- spec.add_dependency 'legion-logging', '>= 1.2.8'
31
+ spec.add_dependency 'legion-logging', '>= 1.5.0'
32
32
  spec.add_dependency 'legion-settings', '>= 1.3.12'
33
33
  spec.add_dependency 'openssl'
34
34
 
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Gaia
5
7
  module Advisory
8
+ extend Legion::Logging::Helper
9
+
6
10
  module_function
7
11
 
8
12
  # Fast in-memory advisory. NEVER makes LLM calls.
@@ -15,9 +19,13 @@ module Legion
15
19
  advisory[:valence] = Gaia.last_valences if Gaia.last_valences
16
20
  merge_tick_data!(advisory, Gaia.registry&.tick_host&.last_tick_result)
17
21
  merge_observer_data!(advisory, caller)
18
- advisory.compact
22
+ result = advisory.compact
23
+ if result.any?
24
+ log.info("GAIA advisory generated conversation_id=#{conversation_id} keys=#{result.keys.join(',')}")
25
+ end
26
+ result
19
27
  rescue StandardError => e
20
- Legion::Logging.warn("GAIA advisory failed: #{e.message}") if defined?(Legion::Logging)
28
+ handle_exception(e, level: :warn, operation: 'gaia.advisory.advise', conversation_id: conversation_id)
21
29
  nil
22
30
  end
23
31
 
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'singleton'
4
+ require 'legion/logging/helper'
4
5
 
5
6
  module Legion
6
7
  module Gaia
7
8
  class AuditObserver
8
9
  include Singleton
10
+ include Legion::Logging::Helper
9
11
 
10
12
  def initialize
11
13
  @user_prefs = {}
@@ -22,8 +24,13 @@ module Legion
22
24
  record_tool_patterns(event)
23
25
  record_quality(event)
24
26
  end
27
+ identity = event.dig(:caller, :requested_by, :identity)
28
+ log.debug(
29
+ 'AuditObserver processed event ' \
30
+ "identity=#{identity || 'unknown'} tools=#{Array(event[:tools_used]).size}"
31
+ )
25
32
  rescue StandardError => e
26
- Legion::Logging.warn("audit observer error: #{e.message}") if defined?(Legion::Logging)
33
+ handle_exception(e, level: :warn, operation: 'gaia.audit_observer.process_event')
27
34
  end
28
35
 
29
36
  def user_preferences(identity)
@@ -1,14 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Gaia
5
7
  module BondRegistry
8
+ extend Legion::Logging::Helper
9
+
6
10
  module_function
7
11
 
8
12
  def register(identity, role:, priority: :normal)
9
13
  @bonds ||= {}
10
14
  @bonds[identity.to_s] = { identity: identity.to_s, role: role.to_sym, priority: priority.to_sym,
11
15
  since: Time.now.utc }
16
+ log.info("BondRegistry registered identity=#{identity} role=#{role} priority=#{priority}")
12
17
  end
13
18
 
14
19
  def partner?(identity)
@@ -41,12 +46,14 @@ module Legion
41
46
 
42
47
  identities.each { |id| register(id, role: :partner, priority: priority) }
43
48
  end
49
+ log.info("BondRegistry hydrated entries=#{result[:results].size}")
44
50
  rescue StandardError => e
45
- Legion::Logging.warn "BondRegistry hydration failed: #{e.message}" if defined?(Legion::Logging)
51
+ handle_exception(e, level: :warn, operation: 'gaia.bond_registry.hydrate_from_apollo')
46
52
  end
47
53
 
48
54
  def reset!
49
55
  @bonds = {}
56
+ log.debug('BondRegistry reset')
50
57
  end
51
58
 
52
59
  def extract_identity_keys(content)
@@ -40,12 +40,13 @@ module Legion
40
40
 
41
41
  def deliver(output_frame)
42
42
  adapter = adapter_for(output_frame.channel_id)
43
- return { delivered: false, reason: :no_adapter } unless adapter
44
- return { delivered: false, reason: :adapter_stopped } unless adapter.started?
43
+ return { delivered: false, reason: :no_adapter, channel_id: output_frame.channel_id } unless adapter
44
+ unless adapter.started?
45
+ return { delivered: false, reason: :adapter_stopped, channel_id: output_frame.channel_id }
46
+ end
45
47
 
46
48
  rendered = adapter.translate_outbound(output_frame)
47
- adapter.deliver(rendered)
48
- { delivered: true, channel_id: output_frame.channel_id }
49
+ normalize_delivery_result(adapter.deliver(rendered), channel_id: output_frame.channel_id)
49
50
  end
50
51
 
51
52
  def start_all
@@ -55,6 +56,19 @@ module Legion
55
56
  def stop_all
56
57
  @mutex.synchronize { @adapters.each_value(&:stop) }
57
58
  end
59
+
60
+ private
61
+
62
+ def normalize_delivery_result(result, channel_id:)
63
+ return { delivered: false, reason: :adapter_returned_false, channel_id: channel_id } if result == false
64
+ return { delivered: true, channel_id: channel_id } unless result.is_a?(Hash)
65
+
66
+ normalized = result.dup
67
+ normalized[:channel_id] ||= channel_id
68
+ normalized[:delivered] = false if normalized[:error] && !normalized.key?(:delivered)
69
+ normalized[:delivered] = true unless normalized.key?(:delivered)
70
+ normalized
71
+ end
58
72
  end
59
73
  end
60
74
  end
@@ -3,12 +3,15 @@
3
3
  require 'json'
4
4
  require 'net/http'
5
5
  require 'uri'
6
+ require 'legion/logging/helper'
6
7
  require_relative 'slack/signing_verifier'
7
8
 
8
9
  module Legion
9
10
  module Gaia
10
11
  module Channels
11
12
  class SlackAdapter < ChannelAdapter
13
+ include Legion::Logging::Helper
14
+
12
15
  CAPABILITIES = %i[rich_text threads reactions mentions file_attachment].freeze
13
16
 
14
17
  attr_reader :signing_secret, :bot_token
@@ -58,6 +61,10 @@ module Legion
58
61
 
59
62
  def deliver(rendered_content, webhook: nil)
60
63
  target_webhook = webhook || @default_webhook
64
+ log.info(
65
+ 'SlackAdapter delivering ' \
66
+ "mode=#{@bot_token && !target_webhook ? 'api' : 'webhook'} channel=#{channel_id}"
67
+ )
61
68
  return deliver_via_api(rendered_content) if @bot_token && !target_webhook
62
69
 
63
70
  deliver_via_webhook(rendered_content, target_webhook)
@@ -72,6 +79,7 @@ module Legion
72
79
 
73
80
  channel = dm_result[:channel_id]
74
81
  rendered = translate_outbound(output_frame).merge(channel: channel)
82
+ log.info("SlackAdapter proactive delivery user_id=#{user_id} channel=#{channel}")
75
83
  deliver_via_api_to_channel(rendered)
76
84
  end
77
85
 
@@ -85,9 +93,11 @@ module Legion
85
93
  )
86
94
  return result if result.is_a?(Hash) && result[:error]
87
95
 
88
- { channel_id: result[:channel_id] || result['channel']&.dig('id') || result['channel'] }
96
+ channel_id = result[:channel_id] || result['channel']&.dig('id') || result['channel']
97
+ log.info("SlackAdapter opened DM user_id=#{user_id} channel=#{channel_id}")
98
+ { channel_id: channel_id }
89
99
  rescue StandardError => e
90
- Legion::Logging.warn("SlackAdapter open_dm failed: #{e.message}") if defined?(Legion::Logging)
100
+ handle_exception(e, level: :warn, operation: 'gaia.channels.slack_adapter.open_dm', user_id: user_id)
91
101
  { error: :open_dm_failed, message: e.message }
92
102
  end
93
103
 
@@ -122,11 +132,13 @@ module Legion
122
132
 
123
133
  def deliver_via_webhook(content, webhook)
124
134
  unless slack_runner_available?
135
+ log.error('SlackAdapter deliver_via_webhook failed error=slack_runner_not_available')
125
136
  return { error: :slack_runner_not_available,
126
137
  message: 'lex-slack Chat runner not loaded' }
127
138
  end
128
139
 
129
140
  message = content.is_a?(Hash) ? content[:text] : content.to_s
141
+ log.info("SlackAdapter delivering via webhook message_length=#{message.length}")
130
142
  Legion::Extensions::Slack::Runners::Chat.send(message: message, webhook: webhook)
131
143
  end
132
144
 
@@ -137,6 +149,8 @@ module Legion
137
149
  channel = (content.is_a?(Hash) && content[:channel]) || @channel_id.to_s
138
150
  post_to_slack_api(channel: channel, text: text)
139
151
  rescue StandardError => e
152
+ handle_exception(e, level: :warn, operation: 'gaia.channels.slack_adapter.deliver_via_api',
153
+ channel: channel)
140
154
  { error: :network_error, message: e.message }
141
155
  end
142
156
 
@@ -154,22 +168,28 @@ module Legion
154
168
  parsed = ::JSON.parse(response.body, symbolize_names: true)
155
169
 
156
170
  if parsed[:ok]
171
+ log.info("SlackAdapter delivered via API channel=#{channel}")
157
172
  { delivered: true, ts: parsed[:ts] }
158
173
  else
174
+ log.error("SlackAdapter API delivery failed channel=#{channel} error=#{parsed[:error] || :unknown_error}")
159
175
  { error: parsed[:error] || :unknown_error }
160
176
  end
161
177
  rescue StandardError => e
178
+ handle_exception(e, level: :warn, operation: 'gaia.channels.slack_adapter.post_to_slack_api',
179
+ channel: channel)
162
180
  { error: :network_error, message: e.message }
163
181
  end
164
182
 
165
183
  def deliver_via_api_to_channel(content)
166
184
  unless slack_runner_available?
185
+ log.error('SlackAdapter deliver_via_api_to_channel failed error=slack_runner_not_available')
167
186
  return { error: :slack_runner_not_available,
168
187
  message: 'lex-slack Chat runner not loaded' }
169
188
  end
170
189
 
171
190
  message = content.is_a?(Hash) ? content[:text] : content.to_s
172
191
  channel = content.is_a?(Hash) ? content[:channel] : nil
192
+ log.info("SlackAdapter sending proactive API message channel=#{channel}")
173
193
  Legion::Extensions::Slack::Runners::Chat.send(
174
194
  message: message,
175
195
  channel: channel,
@@ -4,12 +4,15 @@ require 'net/http'
4
4
  require 'uri'
5
5
  require 'openssl'
6
6
  require 'base64'
7
+ require 'legion/logging/helper'
7
8
 
8
9
  module Legion
9
10
  module Gaia
10
11
  module Channels
11
12
  module Teams
12
13
  module BotFrameworkAuth
14
+ extend Legion::Logging::Helper
15
+
13
16
  OPENID_METADATA_URL = 'https://login.botframework.com/v1/.well-known/openidconfiguration'
14
17
  BOT_FRAMEWORK_ISSUER = 'https://api.botframework.com'
15
18
  EMULATOR_ISSUER = 'https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/'
@@ -66,9 +69,7 @@ module Legion
66
69
  decoded = Base64.urlsafe_decode64(padded)
67
70
  ::JSON.parse(decoded)
68
71
  rescue StandardError => e
69
- if defined?(Legion::Logging)
70
- Legion::Logging.debug("BotFrameworkAuth JWT segment decode failed: #{e.message}")
71
- end
72
+ handle_exception(e, level: :debug, operation: 'gaia.channels.teams.bot_framework_auth.decode_jwt_segment')
72
73
  nil
73
74
  end
74
75
 
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Gaia
5
7
  module Channels
6
8
  module Teams
7
9
  class WebhookHandler
10
+ include Legion::Logging::Helper
11
+
8
12
  attr_reader :adapter
9
13
 
10
14
  def initialize(adapter)
@@ -22,6 +26,7 @@ module Legion
22
26
  end
23
27
 
24
28
  activity_type = activity['type'] || activity[:type]
29
+ log.info("WebhookHandler received activity_type=#{activity_type}")
25
30
  case activity_type
26
31
  when 'message' then handle_message(activity)
27
32
  when 'conversationUpdate' then handle_conversation_update(activity)
@@ -37,6 +42,7 @@ module Legion
37
42
  return error_response(:translate_failed, 'Could not translate activity') unless frame
38
43
 
39
44
  Legion::Gaia.ingest(frame) if Legion::Gaia.respond_to?(:ingest)
45
+ log.info("WebhookHandler ingested frame_id=#{frame.id}")
40
46
  success_response(:message_ingested, frame.id)
41
47
  end
42
48
 
@@ -68,7 +74,7 @@ module Legion
68
74
 
69
75
  ::JSON.parse(body)
70
76
  rescue StandardError => e
71
- Legion::Logging.debug("WebhookHandler activity parse failed: #{e.message}") if defined?(Legion::Logging)
77
+ handle_exception(e, level: :debug, operation: 'gaia.channels.teams.webhook_handler.parse_activity')
72
78
  nil
73
79
  end
74
80
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
3
4
  require_relative 'teams/bot_framework_auth'
4
5
  require_relative 'teams/conversation_store'
5
6
  require_relative 'teams/webhook_handler'
@@ -8,6 +9,8 @@ module Legion
8
9
  module Gaia
9
10
  module Channels
10
11
  class TeamsAdapter < ChannelAdapter
12
+ include Legion::Logging::Helper
13
+
11
14
  CAPABILITIES = %i[rich_text adaptive_cards proactive_messaging mobile desktop mentions].freeze
12
15
  MOBILE_CAPABILITIES = %i[rich_text adaptive_cards mobile mentions].freeze
13
16
  DESKTOP_CAPABILITIES = %i[rich_text adaptive_cards desktop mentions file_attachment].freeze
@@ -33,6 +36,7 @@ module Legion
33
36
 
34
37
  def update_presence_status(status)
35
38
  @last_presence_status = status
39
+ log.info("TeamsAdapter presence updated status=#{status}")
36
40
  end
37
41
 
38
42
  def translate_inbound(activity)
@@ -44,6 +48,7 @@ module Legion
44
48
  text = strip_mention(text, activity)
45
49
 
46
50
  conversation_store.store_from_activity(activity)
51
+ log.debug("TeamsAdapter translated inbound activity_id=#{activity['id'] || activity[:id]}")
47
52
 
48
53
  InputFrame.new(
49
54
  content: text.strip,
@@ -75,8 +80,12 @@ module Legion
75
80
  def deliver(rendered_content, conversation_id: nil)
76
81
  resolved_id = conversation_id || @default_conversation_id
77
82
  ref = resolved_id && conversation_store.lookup(resolved_id)
78
- return { error: :no_conversation_reference } unless ref
83
+ unless ref
84
+ log.error("TeamsAdapter deliver failed conversation_id=#{resolved_id} error=no_conversation_reference")
85
+ return { error: :no_conversation_reference }
86
+ end
79
87
 
88
+ log.info("TeamsAdapter delivering conversation_id=#{ref.conversation_id}")
80
89
  deliver_via_bot(rendered_content, ref)
81
90
  end
82
91
 
@@ -88,6 +97,7 @@ module Legion
88
97
  return conversation_id if conversation_id.is_a?(Hash) && conversation_id[:error]
89
98
 
90
99
  rendered = translate_outbound(output_frame)
100
+ log.info("TeamsAdapter proactive delivery user_id=#{user_id} conversation_id=#{conversation_id}")
91
101
  deliver(rendered, conversation_id: conversation_id)
92
102
  end
93
103
 
@@ -113,11 +123,11 @@ module Legion
113
123
  service_url: service_url,
114
124
  tenant_id: resolved_tenant
115
125
  )
126
+ log.info("TeamsAdapter created proactive conversation user_id=#{user_id} conversation_id=#{conversation_id}")
116
127
  conversation_id
117
128
  rescue StandardError => e
118
- if defined?(Legion::Logging)
119
- Legion::Logging.warn("TeamsAdapter create_proactive_conversation failed: #{e.message}")
120
- end
129
+ handle_exception(e, level: :warn, operation: 'gaia.channels.teams_adapter.create_proactive_conversation',
130
+ user_id: user_id, tenant_id: resolved_tenant)
121
131
  { error: :create_conversation_failed, message: e.message }
122
132
  end
123
133
 
@@ -187,16 +197,19 @@ module Legion
187
197
 
188
198
  def deliver_via_bot(rendered_content, ref)
189
199
  unless bot_runner_available?
200
+ log.error('TeamsAdapter deliver_via_bot failed error=bot_runner_not_available')
190
201
  return { error: :bot_runner_not_available,
191
202
  message: 'lex-microsoft_teams Bot runner not loaded' }
192
203
  end
193
204
 
194
205
  bot = Legion::Extensions::MicrosoftTeams::Client.new
195
206
  if rendered_content.is_a?(Hash) && rendered_content[:type] == 'adaptive_card'
207
+ log.info("TeamsAdapter sending adaptive card conversation_id=#{ref.conversation_id}")
196
208
  bot.send_card(service_url: ref.service_url, conversation_id: ref.conversation_id,
197
209
  card: rendered_content[:card])
198
210
  else
199
211
  text = rendered_content.is_a?(Hash) ? rendered_content[:text] : rendered_content.to_s
212
+ log.info("TeamsAdapter sending text conversation_id=#{ref.conversation_id}")
200
213
  bot.send_text(service_url: ref.service_url, conversation_id: ref.conversation_id,
201
214
  text: text)
202
215
  end
@@ -208,7 +221,13 @@ module Legion
208
221
 
209
222
  def resolve_proactive_conversation(user_id)
210
223
  existing = conversation_store.conversations_for_user(user_id)
211
- return existing.first.conversation_id unless existing.empty?
224
+ unless existing.empty?
225
+ log.debug(
226
+ 'TeamsAdapter reused proactive conversation ' \
227
+ "user_id=#{user_id} conversation_id=#{existing.first.conversation_id}"
228
+ )
229
+ return existing.first.conversation_id
230
+ end
212
231
 
213
232
  create_proactive_conversation(user_id: user_id)
214
233
  end
@@ -1,21 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Gaia
5
7
  module Logging
6
- private
7
-
8
- def log_debug(msg)
9
- Legion::Logging.debug(msg) if Legion.const_defined?(:Logging, false)
10
- end
11
-
12
- def log_info(msg)
13
- Legion::Logging.info(msg) if Legion.const_defined?(:Logging, false)
14
- end
15
-
16
- def log_warn(msg)
17
- Legion::Logging.warn(msg) if Legion.const_defined?(:Logging, false)
18
- end
8
+ include Legion::Logging::Helper
19
9
  end
20
10
  end
21
11
  end
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Gaia
5
7
  class NotificationGate
6
8
  class ScheduleEvaluator
9
+ include Legion::Logging::Helper
10
+
7
11
  DAY_NAMES = %w[sun mon tue wed thu fri sat].freeze
8
12
 
9
13
  # Standard (non-DST) offsets for known IANA zone names.
@@ -104,7 +108,9 @@ module Legion
104
108
  abs_h = hours.abs.to_i
105
109
  abs_m = ((hours.abs % 1) * 60).round
106
110
  format('%<sign>s%<h>02d:%<m>02d', sign: sign, h: abs_h, m: abs_m)
107
- rescue StandardError
111
+ rescue StandardError => e
112
+ handle_exception(e, level: :debug, operation: 'gaia.notification_gate.schedule_evaluator.tzinfo_offset',
113
+ timezone: timezone)
108
114
  nil
109
115
  end
110
116
 
@@ -1,14 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Gaia
5
7
  module OfflineHandler
8
+ extend Legion::Logging::Helper
9
+
6
10
  DEFAULT_OFFLINE_MESSAGE = 'The agent is currently offline. Your message has been queued.'
7
11
 
8
12
  class << self
9
13
  def handle_offline_delivery(input_frame, worker_id:)
10
14
  queue_message(input_frame, worker_id)
11
15
  notify_sender(input_frame)
16
+ log.info(
17
+ 'OfflineHandler queued ' \
18
+ "frame_id=#{input_frame.respond_to?(:id) ? input_frame.id : 'unknown'} worker_id=#{worker_id}"
19
+ )
12
20
  { queued: true, worker_id: worker_id }
13
21
  end
14
22
 
@@ -21,6 +29,7 @@ module Legion
21
29
 
22
30
  def record_presence(worker_id)
23
31
  presence_store[worker_id] = { last_seen: Time.now }
32
+ log.debug("OfflineHandler recorded presence worker_id=#{worker_id}")
24
33
  end
25
34
 
26
35
  def pending_count(worker_id)
@@ -28,7 +37,9 @@ module Legion
28
37
  end
29
38
 
30
39
  def drain_pending(worker_id)
31
- pending_store.delete(worker_id) || []
40
+ drained = pending_store.delete(worker_id) || []
41
+ log.info("OfflineHandler drained pending worker_id=#{worker_id} count=#{drained.size}") if drained.any?
42
+ drained
32
43
  end
33
44
 
34
45
  def reset!
@@ -56,8 +67,10 @@ module Legion
56
67
  session_continuity_id: frame.respond_to?(:session_continuity_id) ? frame.session_continuity_id : nil
57
68
  )
58
69
  registry.deliver(output)
70
+ log.info("OfflineHandler notified sender frame_id=#{output.id} channel=#{frame.channel_id}")
59
71
  rescue StandardError => e
60
- Legion::Logging.warn("OfflineHandler notify_sender failed: #{e.message}") if defined?(Legion::Logging)
72
+ handle_exception(e, level: :warn, operation: 'gaia.offline_handler.notify_sender',
73
+ channel_id: frame.respond_to?(:channel_id) ? frame.channel_id : nil)
61
74
  nil
62
75
  end
63
76
 
@@ -68,9 +81,7 @@ module Legion
68
81
  60
69
82
  end
70
83
  rescue StandardError => e
71
- if defined?(Legion::Logging)
72
- Legion::Logging.debug("OfflineHandler offline_threshold settings unavailable, using default: #{e.message}")
73
- end
84
+ handle_exception(e, level: :debug, operation: 'gaia.offline_handler.offline_threshold')
74
85
  60
75
86
  end
76
87