legion-gaia 0.9.30 → 0.9.32

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: f556012977257412277b2c3f5cfa538a047171f2b9ff6fded4ddcf6afca4307c
4
- data.tar.gz: 215d8b58082f8d088497bae4fe20f08e419bd09ab0426e59e1ab5017bd51f3a7
3
+ metadata.gz: 1bbf0da289685511832818d84f1b2ec21b91973232f4bda1b150e36735e47e74
4
+ data.tar.gz: 55fa7f257b87b4f7cca3b08954b7017f2d778338f66254c78613c14d0f60e04c
5
5
  SHA512:
6
- metadata.gz: b16f2dda6e9a6c9a87d8fa7c0f7fe3df0d835621cc4272d101472962d09de24afd27b180455836dd38f8baa0729634088b2e4b609de9071c6e0ee2caf249c2bb
7
- data.tar.gz: 4e8121a50f2e55afc75fb88cde587027a0f2d6faaae73c5fef6e2b21c6bb1b024c9ab19bf5a6ebe9b6b0dc55e34b698d13766dd9fca2f9e3bbe36ef1e67f40a0
6
+ metadata.gz: ffa8662d7993c4bc942a0783e16becbb5e8452d8332124e8939aacbf3016d0261a9b14198cbe9ef8f6942e101dc97dd071d774a9cf91db2f554bd8bb9e73bf7b
7
+ data.tar.gz: ba1d6014b49e7843b78012b5a104a299e8d909fbdf965bc936ebe8a870c09792ef82e871bdb1c18224e6b543e929c49b5206acef7a93fb86d9fdf2e8fefc7c52
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.32] - 2026-03-31
4
+
5
+ ### Added
6
+ - Partner absence emotional response: heartbeat tracks consecutive ticks without partner observations when prediction_engine phase is wired
7
+ - `check_partner_absence` injects absence valence into `@last_valences` with logarithmic importance scaling (0.4 base, capped at 0.7)
8
+ - Absence valence uses lex-agentic-affect `Helpers::Valence.absence_importance` when available, inline fallback otherwise
9
+ - `@last_valences` cleared on shutdown to prevent state leakage
10
+
11
+ ## [0.9.31] - 2026-03-31
12
+
13
+ ### Added
14
+ - NotificationGate signal feeds: heartbeat feeds arousal from emotional_evaluation valence and Teams presence status into the gate
15
+ - `process_delayed` call in heartbeat — delayed notification frames are now retried each tick
16
+ - `last_presence_status` and `update_presence_status` on TeamsAdapter for presence signal tracking
17
+ - `@notification_gate` stored as instance variable for direct heartbeat access
18
+
3
19
  ## [0.9.30] - 2026-03-31
4
20
 
5
21
  ### Added
@@ -12,7 +12,7 @@ module Legion
12
12
  MOBILE_CAPABILITIES = %i[rich_text adaptive_cards mobile mentions].freeze
13
13
  DESKTOP_CAPABILITIES = %i[rich_text adaptive_cards desktop mentions file_attachment].freeze
14
14
 
15
- attr_reader :conversation_store, :app_id
15
+ attr_reader :conversation_store, :app_id, :last_presence_status
16
16
 
17
17
  def self.from_settings(settings)
18
18
  return nil unless settings&.dig(:channels, :teams, :enabled)
@@ -24,6 +24,11 @@ module Legion
24
24
  super(channel_id: :teams, capabilities: CAPABILITIES)
25
25
  @app_id = app_id
26
26
  @conversation_store = Teams::ConversationStore.new
27
+ @last_presence_status = nil
28
+ end
29
+
30
+ def update_presence_status(status)
31
+ @last_presence_status = status
27
32
  end
28
33
 
29
34
  def translate_inbound(activity)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Gaia
5
- VERSION = '0.9.30'
5
+ VERSION = '0.9.32'
6
6
  end
7
7
  end
data/lib/legion/gaia.rb CHANGED
@@ -84,9 +84,12 @@ module Legion
84
84
  @channel_registry = nil
85
85
  @output_router = nil
86
86
  @session_store = nil
87
+ @notification_gate = nil
87
88
  @router_bridge = nil
88
89
  @agent_bridge = nil
89
90
  @partner_observations = nil
91
+ @partner_absence_misses = 0
92
+ @last_valences = nil
90
93
 
91
94
  log_info 'Legion::Gaia shut down'
92
95
  end
@@ -103,7 +106,7 @@ module Legion
103
106
  end
104
107
  end
105
108
 
106
- def heartbeat(**)
109
+ def heartbeat(**) # rubocop:disable Metrics/MethodLength
107
110
  return { error: :not_started } unless started?
108
111
 
109
112
  signals = @sensory_buffer.drain
@@ -135,6 +138,11 @@ module Legion
135
138
  tick_host.last_tick_result = result
136
139
  end
137
140
 
141
+ check_partner_absence(observations, phase_handlers)
142
+
143
+ feed_notification_gate(result)
144
+ @output_router&.process_delayed
145
+
138
146
  maybe_flush_trackers
139
147
 
140
148
  result
@@ -191,6 +199,7 @@ module Legion
191
199
  def boot_agent
192
200
  @tick_unavailable_warned = false
193
201
  @partner_observations = []
202
+ @partner_absence_misses = 0
194
203
  @sensory_buffer = SensoryBuffer.new
195
204
  @registry = Registry.instance
196
205
  @registry.reset!
@@ -234,9 +243,9 @@ module Legion
234
243
  @session_store = SessionStore.new(ttl: settings&.dig(:session, :ttl) || 86_400)
235
244
 
236
245
  renderer = ChannelAwareRenderer.new(settings: settings || {})
237
- notification_gate = NotificationGate.new(settings: settings || {})
246
+ @notification_gate = NotificationGate.new(settings: settings || {})
238
247
  @output_router = OutputRouter.new(channel_registry: @channel_registry, renderer: renderer,
239
- notification_gate: notification_gate)
248
+ notification_gate: @notification_gate)
240
249
 
241
250
  ChannelAdapter.adapter_classes.each do |klass|
242
251
  adapter = klass.from_settings(settings)
@@ -336,6 +345,77 @@ module Legion
336
345
  nil
337
346
  end
338
347
 
348
+ def check_partner_absence(observations, phase_handlers)
349
+ has_partner = observations.any? { |o| o[:bond_role] == :partner }
350
+
351
+ if has_partner
352
+ @partner_absence_misses = 0
353
+ return
354
+ end
355
+
356
+ return unless phase_handlers.key?(:prediction_engine)
357
+
358
+ @partner_absence_misses += 1
359
+ inject_absence_valence(@partner_absence_misses)
360
+ rescue StandardError => e
361
+ log_debug "check_partner_absence error: #{e.message}"
362
+ end
363
+
364
+ def inject_absence_valence(consecutive_misses)
365
+ valence = absence_valence(consecutive_misses)
366
+ return unless valence
367
+
368
+ @last_valences ||= []
369
+ @last_valences.push(valence)
370
+ log_debug "[gaia] partner absence: misses=#{consecutive_misses} " \
371
+ "importance=#{valence[:importance].round(2)}"
372
+ end
373
+
374
+ def absence_valence(consecutive_misses)
375
+ importance = if defined?(Legion::Extensions::Agentic::Affect::Emotion::Helpers::Valence)
376
+ Legion::Extensions::Agentic::Affect::Emotion::Helpers::Valence
377
+ .absence_importance(consecutive_misses)
378
+ else
379
+ [0.4 + (0.1 * Math.log(consecutive_misses + 1)), 0.7].min
380
+ end
381
+
382
+ { urgency: 0.2, importance: importance, novelty: 0.1, familiarity: 0.8 }
383
+ end
384
+
385
+ def feed_notification_gate(result)
386
+ return unless @notification_gate && result.is_a?(Hash) && result[:results]
387
+
388
+ if (valence = result.dig(:results, :emotional_evaluation, :valence))
389
+ arousal = compute_arousal(valence)
390
+ @notification_gate.update_behavioral(arousal: arousal) if arousal
391
+ end
392
+
393
+ feed_presence_to_gate
394
+ rescue StandardError => e
395
+ log_debug "feed_notification_gate error: #{e.message}"
396
+ end
397
+
398
+ def compute_arousal(valence)
399
+ return nil unless valence.is_a?(Hash)
400
+
401
+ urgency = valence[:urgency].to_f
402
+ novelty = valence[:novelty].to_f
403
+ importance = valence[:importance].to_f
404
+ ((urgency + novelty + importance) / 3.0).clamp(0.0, 1.0)
405
+ end
406
+
407
+ def feed_presence_to_gate
408
+ return unless @notification_gate && @channel_registry
409
+
410
+ teams_adapter = @channel_registry.adapter_for(:teams)
411
+ return unless teams_adapter.respond_to?(:last_presence_status)
412
+
413
+ status = teams_adapter.last_presence_status
414
+ @notification_gate.update_presence(availability: status) if status
415
+ rescue StandardError => e
416
+ log_debug "feed_presence_to_gate error: #{e.message}"
417
+ end
418
+
339
419
  def maybe_flush_trackers
340
420
  return unless TrackerPersistence.should_flush?
341
421
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-gaia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.30
4
+ version: 0.9.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity