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 +4 -4
- data/CHANGELOG.md +16 -0
- data/lib/legion/gaia/channels/teams_adapter.rb +6 -1
- data/lib/legion/gaia/version.rb +1 -1
- data/lib/legion/gaia.rb +83 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bbf0da289685511832818d84f1b2ec21b91973232f4bda1b150e36735e47e74
|
|
4
|
+
data.tar.gz: 55fa7f257b87b4f7cca3b08954b7017f2d778338f66254c78613c14d0f60e04c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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)
|
data/lib/legion/gaia/version.rb
CHANGED
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
|
|