lex-agentic-affect 0.1.0
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 +7 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +13 -0
- data/lex-agentic-affect.gemspec +30 -0
- data/lib/legion/extensions/agentic/affect/appraisal/client.rb +20 -0
- data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal.rb +112 -0
- data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine.rb +129 -0
- data/lib/legion/extensions/agentic/affect/appraisal/helpers/constants.rb +43 -0
- data/lib/legion/extensions/agentic/affect/appraisal/runners/appraisal.rb +105 -0
- data/lib/legion/extensions/agentic/affect/appraisal/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/appraisal.rb +19 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/client.rb +19 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/constants.rb +37 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine.rb +151 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective.rb +92 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy.rb +93 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy.rb +20 -0
- data/lib/legion/extensions/agentic/affect/contagion/client.rb +28 -0
- data/lib/legion/extensions/agentic/affect/contagion/helpers/constants.rb +34 -0
- data/lib/legion/extensions/agentic/affect/contagion/helpers/contagion_engine.rb +184 -0
- data/lib/legion/extensions/agentic/affect/contagion/helpers/meme.rb +97 -0
- data/lib/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion.rb +125 -0
- data/lib/legion/extensions/agentic/affect/contagion/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/contagion.rb +19 -0
- data/lib/legion/extensions/agentic/affect/defusion/client.rb +28 -0
- data/lib/legion/extensions/agentic/affect/defusion/helpers/constants.rb +64 -0
- data/lib/legion/extensions/agentic/affect/defusion/helpers/defusion_engine.rb +167 -0
- data/lib/legion/extensions/agentic/affect/defusion/helpers/thought.rb +92 -0
- data/lib/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion.rb +127 -0
- data/lib/legion/extensions/agentic/affect/defusion/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/defusion.rb +19 -0
- data/lib/legion/extensions/agentic/affect/emotion/actors/momentum_decay.rb +45 -0
- data/lib/legion/extensions/agentic/affect/emotion/client.rb +36 -0
- data/lib/legion/extensions/agentic/affect/emotion/helpers/baseline.rb +52 -0
- data/lib/legion/extensions/agentic/affect/emotion/helpers/momentum.rb +52 -0
- data/lib/legion/extensions/agentic/affect/emotion/helpers/valence.rb +92 -0
- data/lib/legion/extensions/agentic/affect/emotion/runners/gut.rb +102 -0
- data/lib/legion/extensions/agentic/affect/emotion/runners/valence.rb +120 -0
- data/lib/legion/extensions/agentic/affect/emotion/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/emotion.rb +20 -0
- data/lib/legion/extensions/agentic/affect/empathy/client.rb +21 -0
- data/lib/legion/extensions/agentic/affect/empathy/helpers/constants.rb +54 -0
- data/lib/legion/extensions/agentic/affect/empathy/helpers/mental_model.rb +185 -0
- data/lib/legion/extensions/agentic/affect/empathy/helpers/model_store.rb +88 -0
- data/lib/legion/extensions/agentic/affect/empathy/runners/empathy.rb +173 -0
- data/lib/legion/extensions/agentic/affect/empathy/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/empathy.rb +20 -0
- data/lib/legion/extensions/agentic/affect/fatigue/client.rb +26 -0
- data/lib/legion/extensions/agentic/affect/fatigue/helpers/constants.rb +54 -0
- data/lib/legion/extensions/agentic/affect/fatigue/helpers/energy_model.rb +181 -0
- data/lib/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store.rb +146 -0
- data/lib/legion/extensions/agentic/affect/fatigue/runners/fatigue.rb +89 -0
- data/lib/legion/extensions/agentic/affect/fatigue/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/fatigue.rb +19 -0
- data/lib/legion/extensions/agentic/affect/flow/client.rb +25 -0
- data/lib/legion/extensions/agentic/affect/flow/helpers/constants.rb +84 -0
- data/lib/legion/extensions/agentic/affect/flow/helpers/flow_detector.rb +166 -0
- data/lib/legion/extensions/agentic/affect/flow/runners/flow.rb +129 -0
- data/lib/legion/extensions/agentic/affect/flow/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/flow.rb +18 -0
- data/lib/legion/extensions/agentic/affect/interoception/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/affect/interoception/client.rb +28 -0
- data/lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb +152 -0
- data/lib/legion/extensions/agentic/affect/interoception/helpers/constants.rb +68 -0
- data/lib/legion/extensions/agentic/affect/interoception/helpers/somatic_marker.rb +75 -0
- data/lib/legion/extensions/agentic/affect/interoception/runners/interoception.rb +101 -0
- data/lib/legion/extensions/agentic/affect/interoception/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/interoception.rb +20 -0
- data/lib/legion/extensions/agentic/affect/mood/client.rb +21 -0
- data/lib/legion/extensions/agentic/affect/mood/helpers/constants.rb +78 -0
- data/lib/legion/extensions/agentic/affect/mood/helpers/mood_state.rb +154 -0
- data/lib/legion/extensions/agentic/affect/mood/runners/mood.rb +122 -0
- data/lib/legion/extensions/agentic/affect/mood/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/mood.rb +18 -0
- data/lib/legion/extensions/agentic/affect/motivation/client.rb +26 -0
- data/lib/legion/extensions/agentic/affect/motivation/helpers/constants.rb +48 -0
- data/lib/legion/extensions/agentic/affect/motivation/helpers/drive_state.rb +98 -0
- data/lib/legion/extensions/agentic/affect/motivation/helpers/motivation_store.rb +106 -0
- data/lib/legion/extensions/agentic/affect/motivation/runners/motivation.rb +165 -0
- data/lib/legion/extensions/agentic/affect/motivation/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/motivation.rb +19 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate.rb +45 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/client.rb +28 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/constants.rb +82 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event.rb +98 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer.rb +88 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine.rb +153 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal.rb +164 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/reappraisal.rb +20 -0
- data/lib/legion/extensions/agentic/affect/regulation/client.rb +25 -0
- data/lib/legion/extensions/agentic/affect/regulation/helpers/constants.rb +71 -0
- data/lib/legion/extensions/agentic/affect/regulation/helpers/regulation_model.rb +175 -0
- data/lib/legion/extensions/agentic/affect/regulation/runners/emotional_regulation.rb +127 -0
- data/lib/legion/extensions/agentic/affect/regulation/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/regulation.rb +18 -0
- data/lib/legion/extensions/agentic/affect/resilience/client.rb +27 -0
- data/lib/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker.rb +130 -0
- data/lib/legion/extensions/agentic/affect/resilience/helpers/constants.rb +79 -0
- data/lib/legion/extensions/agentic/affect/resilience/helpers/resilience_model.rb +165 -0
- data/lib/legion/extensions/agentic/affect/resilience/runners/resilience.rb +150 -0
- data/lib/legion/extensions/agentic/affect/resilience/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/resilience.rb +19 -0
- data/lib/legion/extensions/agentic/affect/resonance/client.rb +24 -0
- data/lib/legion/extensions/agentic/affect/resonance/helpers/category.rb +75 -0
- data/lib/legion/extensions/agentic/affect/resonance/helpers/constants.rb +47 -0
- data/lib/legion/extensions/agentic/affect/resonance/helpers/resonance_engine.rb +115 -0
- data/lib/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance.rb +94 -0
- data/lib/legion/extensions/agentic/affect/resonance/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/resonance.rb +19 -0
- data/lib/legion/extensions/agentic/affect/reward/client.rb +26 -0
- data/lib/legion/extensions/agentic/affect/reward/helpers/constants.rb +67 -0
- data/lib/legion/extensions/agentic/affect/reward/helpers/reward_signal.rb +178 -0
- data/lib/legion/extensions/agentic/affect/reward/helpers/reward_store.rb +142 -0
- data/lib/legion/extensions/agentic/affect/reward/runners/reward.rb +92 -0
- data/lib/legion/extensions/agentic/affect/reward/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/reward.rb +19 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/client.rb +29 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/body_state.rb +69 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/constants.rb +43 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store.rb +160 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker.rb +74 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker.rb +132 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker.rb +20 -0
- data/lib/legion/extensions/agentic/affect/version.rb +11 -0
- data/lib/legion/extensions/agentic/affect.rb +34 -0
- data/spec/legion/extensions/agentic/affect/appraisal/client_spec.rb +52 -0
- data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine_spec.rb +161 -0
- data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_spec.rb +175 -0
- data/spec/legion/extensions/agentic/affect/appraisal/helpers/constants_spec.rb +49 -0
- data/spec/legion/extensions/agentic/affect/appraisal/runners/appraisal_spec.rb +116 -0
- data/spec/legion/extensions/agentic/affect/cognitive_empathy/client_spec.rb +62 -0
- data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine_spec.rb +316 -0
- data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective_spec.rb +132 -0
- data/spec/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy_spec.rb +200 -0
- data/spec/legion/extensions/agentic/affect/contagion/client_spec.rb +63 -0
- data/spec/legion/extensions/agentic/affect/contagion/helpers/constants_spec.rb +86 -0
- data/spec/legion/extensions/agentic/affect/contagion/helpers/contagion_engine_spec.rb +241 -0
- data/spec/legion/extensions/agentic/affect/contagion/helpers/meme_spec.rb +160 -0
- data/spec/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion_spec.rb +211 -0
- data/spec/legion/extensions/agentic/affect/defusion/client_spec.rb +80 -0
- data/spec/legion/extensions/agentic/affect/defusion/helpers/constants_spec.rb +84 -0
- data/spec/legion/extensions/agentic/affect/defusion/helpers/defusion_engine_spec.rb +250 -0
- data/spec/legion/extensions/agentic/affect/defusion/helpers/thought_spec.rb +178 -0
- data/spec/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion_spec.rb +185 -0
- data/spec/legion/extensions/agentic/affect/emotion/actors/momentum_decay_spec.rb +46 -0
- data/spec/legion/extensions/agentic/affect/emotion/client_spec.rb +46 -0
- data/spec/legion/extensions/agentic/affect/emotion/helpers/baseline_spec.rb +48 -0
- data/spec/legion/extensions/agentic/affect/emotion/helpers/momentum_spec.rb +45 -0
- data/spec/legion/extensions/agentic/affect/emotion/helpers/valence_spec.rb +91 -0
- data/spec/legion/extensions/agentic/affect/emotion/runners/gut_spec.rb +73 -0
- data/spec/legion/extensions/agentic/affect/emotion/runners/valence_spec.rb +67 -0
- data/spec/legion/extensions/agentic/affect/empathy/client_spec.rb +20 -0
- data/spec/legion/extensions/agentic/affect/empathy/helpers/constants_spec.rb +23 -0
- data/spec/legion/extensions/agentic/affect/empathy/helpers/mental_model_spec.rb +150 -0
- data/spec/legion/extensions/agentic/affect/empathy/helpers/model_store_spec.rb +94 -0
- data/spec/legion/extensions/agentic/affect/empathy/runners/empathy_spec.rb +127 -0
- data/spec/legion/extensions/agentic/affect/fatigue/client_spec.rb +66 -0
- data/spec/legion/extensions/agentic/affect/fatigue/helpers/constants_spec.rb +130 -0
- data/spec/legion/extensions/agentic/affect/fatigue/helpers/energy_model_spec.rb +281 -0
- data/spec/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store_spec.rb +157 -0
- data/spec/legion/extensions/agentic/affect/fatigue/runners/fatigue_spec.rb +127 -0
- data/spec/legion/extensions/agentic/affect/flow/client_spec.rb +58 -0
- data/spec/legion/extensions/agentic/affect/flow/helpers/constants_spec.rb +112 -0
- data/spec/legion/extensions/agentic/affect/flow/helpers/flow_detector_spec.rb +268 -0
- data/spec/legion/extensions/agentic/affect/flow/runners/flow_spec.rb +222 -0
- data/spec/legion/extensions/agentic/affect/interoception/client_spec.rb +52 -0
- data/spec/legion/extensions/agentic/affect/interoception/helpers/body_budget_spec.rb +178 -0
- data/spec/legion/extensions/agentic/affect/interoception/helpers/somatic_marker_spec.rb +120 -0
- data/spec/legion/extensions/agentic/affect/interoception/runners/interoception_spec.rb +108 -0
- data/spec/legion/extensions/agentic/affect/mood/client_spec.rb +20 -0
- data/spec/legion/extensions/agentic/affect/mood/helpers/constants_spec.rb +29 -0
- data/spec/legion/extensions/agentic/affect/mood/helpers/mood_state_spec.rb +94 -0
- data/spec/legion/extensions/agentic/affect/mood/runners/mood_spec.rb +71 -0
- data/spec/legion/extensions/agentic/affect/motivation/client_spec.rb +35 -0
- data/spec/legion/extensions/agentic/affect/motivation/helpers/constants_spec.rb +111 -0
- data/spec/legion/extensions/agentic/affect/motivation/helpers/drive_state_spec.rb +183 -0
- data/spec/legion/extensions/agentic/affect/motivation/helpers/motivation_store_spec.rb +185 -0
- data/spec/legion/extensions/agentic/affect/motivation/runners/motivation_spec.rb +248 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate_spec.rb +46 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/client_spec.rb +64 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/constants_spec.rb +102 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event_spec.rb +177 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer_spec.rb +161 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine_spec.rb +211 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal_spec.rb +312 -0
- data/spec/legion/extensions/agentic/affect/regulation/client_spec.rb +61 -0
- data/spec/legion/extensions/agentic/affect/regulation/helpers/constants_spec.rb +108 -0
- data/spec/legion/extensions/agentic/affect/regulation/helpers/regulation_model_spec.rb +200 -0
- data/spec/legion/extensions/agentic/affect/regulation/runners/emotional_regulation_spec.rb +190 -0
- data/spec/legion/extensions/agentic/affect/resilience/client_spec.rb +36 -0
- data/spec/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker_spec.rb +164 -0
- data/spec/legion/extensions/agentic/affect/resilience/helpers/constants_spec.rb +78 -0
- data/spec/legion/extensions/agentic/affect/resilience/helpers/resilience_model_spec.rb +133 -0
- data/spec/legion/extensions/agentic/affect/resilience/runners/resilience_spec.rb +150 -0
- data/spec/legion/extensions/agentic/affect/resonance/client_spec.rb +66 -0
- data/spec/legion/extensions/agentic/affect/resonance/cognitive_resonance_spec.rb +27 -0
- data/spec/legion/extensions/agentic/affect/resonance/helpers/category_spec.rb +146 -0
- data/spec/legion/extensions/agentic/affect/resonance/helpers/constants_spec.rb +104 -0
- data/spec/legion/extensions/agentic/affect/resonance/helpers/resonance_engine_spec.rb +189 -0
- data/spec/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance_spec.rb +197 -0
- data/spec/legion/extensions/agentic/affect/reward/client_spec.rb +42 -0
- data/spec/legion/extensions/agentic/affect/reward/helpers/constants_spec.rb +91 -0
- data/spec/legion/extensions/agentic/affect/reward/helpers/reward_signal_spec.rb +296 -0
- data/spec/legion/extensions/agentic/affect/reward/helpers/reward_store_spec.rb +167 -0
- data/spec/legion/extensions/agentic/affect/reward/runners/reward_spec.rb +149 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/client_spec.rb +83 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/body_state_spec.rb +155 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store_spec.rb +233 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker_spec.rb +172 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker_spec.rb +181 -0
- data/spec/spec_helper.rb +46 -0
- metadata +302 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Agentic::Affect::Flow::Helpers::Constants do
|
|
6
|
+
describe 'FLOW_STATES' do
|
|
7
|
+
it 'contains all eight Csikszentmihalyi states' do
|
|
8
|
+
expect(described_class::FLOW_STATES).to contain_exactly(
|
|
9
|
+
:flow, :arousal, :control, :relaxation, :boredom, :apathy, :worry, :anxiety
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'is frozen' do
|
|
14
|
+
expect(described_class::FLOW_STATES).to be_frozen
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe 'FLOW_ZONE' do
|
|
19
|
+
it 'defines challenge and skill boundaries' do
|
|
20
|
+
zone = described_class::FLOW_ZONE
|
|
21
|
+
expect(zone[:challenge_min]).to eq(0.4)
|
|
22
|
+
expect(zone[:challenge_max]).to eq(0.8)
|
|
23
|
+
expect(zone[:skill_min]).to eq(0.4)
|
|
24
|
+
expect(zone[:skill_max]).to eq(0.8)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'defines balance tolerance' do
|
|
28
|
+
expect(described_class::FLOW_ZONE[:balance_tolerance]).to eq(0.15)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'is frozen' do
|
|
32
|
+
expect(described_class::FLOW_ZONE).to be_frozen
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe 'FLOW_ALPHA' do
|
|
37
|
+
it 'is 0.15' do
|
|
38
|
+
expect(described_class::FLOW_ALPHA).to eq(0.15)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe 'DEEP_FLOW_THRESHOLD' do
|
|
43
|
+
it 'is 20 consecutive ticks' do
|
|
44
|
+
expect(described_class::DEEP_FLOW_THRESHOLD).to eq(20)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe 'FLOW_EFFECTS' do
|
|
49
|
+
it 'defines performance and creativity boosts' do
|
|
50
|
+
effects = described_class::FLOW_EFFECTS
|
|
51
|
+
expect(effects[:performance_boost]).to eq(1.15)
|
|
52
|
+
expect(effects[:creativity_boost]).to eq(1.2)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'defines fatigue reduction' do
|
|
56
|
+
expect(described_class::FLOW_EFFECTS[:fatigue_reduction]).to eq(0.5)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'is frozen' do
|
|
60
|
+
expect(described_class::FLOW_EFFECTS).to be_frozen
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe 'FLOW_BREAKERS' do
|
|
65
|
+
it 'contains five breaker types' do
|
|
66
|
+
expect(described_class::FLOW_BREAKERS.size).to eq(5)
|
|
67
|
+
expect(described_class::FLOW_BREAKERS).to include(:high_anxiety, :burnout, :trust_violation)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'is frozen' do
|
|
71
|
+
expect(described_class::FLOW_BREAKERS).to be_frozen
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe 'STATE_REGIONS' do
|
|
76
|
+
it 'defines eight regions' do
|
|
77
|
+
expect(described_class::STATE_REGIONS.size).to eq(8)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'marks only flow as balanced' do
|
|
81
|
+
balanced = described_class::STATE_REGIONS.select { |_, v| v[:balanced] }.keys
|
|
82
|
+
expect(balanced).to eq([:flow])
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'uses Range objects for challenge and skill' do
|
|
86
|
+
described_class::STATE_REGIONS.each_value do |region|
|
|
87
|
+
expect(region[:challenge]).to be_a(Range)
|
|
88
|
+
expect(region[:skill]).to be_a(Range)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe 'score bonuses' do
|
|
94
|
+
it 'defines deep flow bonus' do
|
|
95
|
+
expect(described_class::DEEP_FLOW_BONUS).to eq(0.1)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'defines curiosity bonus' do
|
|
99
|
+
expect(described_class::CURIOSITY_BONUS).to eq(0.05)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'defines low error bonus' do
|
|
103
|
+
expect(described_class::LOW_ERROR_BONUS).to eq(0.05)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe 'MAX_FLOW_HISTORY' do
|
|
108
|
+
it 'caps at 100' do
|
|
109
|
+
expect(described_class::MAX_FLOW_HISTORY).to eq(100)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Agentic::Affect::Flow::Helpers::FlowDetector do
|
|
6
|
+
subject(:detector) { described_class.new }
|
|
7
|
+
|
|
8
|
+
describe '#initialize' do
|
|
9
|
+
it 'starts with neutral challenge and skill' do
|
|
10
|
+
expect(detector.challenge).to eq(0.5)
|
|
11
|
+
expect(detector.skill).to eq(0.5)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'starts in relaxation state' do
|
|
15
|
+
expect(detector.flow_state).to eq(:relaxation)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'starts with zero flow score' do
|
|
19
|
+
expect(detector.flow_score).to eq(0.0)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'starts with zero consecutive and total flow ticks' do
|
|
23
|
+
expect(detector.consecutive_flow_ticks).to eq(0)
|
|
24
|
+
expect(detector.total_flow_ticks).to eq(0)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'starts with empty history' do
|
|
28
|
+
expect(detector.history).to be_empty
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe '#update' do
|
|
33
|
+
it 'applies EMA to challenge and skill' do
|
|
34
|
+
detector.update(challenge_input: 0.8, skill_input: 0.8)
|
|
35
|
+
expect(detector.challenge).to be > 0.5
|
|
36
|
+
expect(detector.skill).to be > 0.5
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'clamps inputs to 0.0..1.0' do
|
|
40
|
+
detector.update(challenge_input: 2.0, skill_input: -1.0)
|
|
41
|
+
expect(detector.challenge).to be_between(0.0, 1.0)
|
|
42
|
+
expect(detector.skill).to be_between(0.0, 1.0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'records a snapshot in history' do
|
|
46
|
+
detector.update(challenge_input: 0.6, skill_input: 0.6)
|
|
47
|
+
expect(detector.history.size).to eq(1)
|
|
48
|
+
expect(detector.history.last).to include(:state, :flow_score, :challenge, :skill, :at)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'caps history at MAX_FLOW_HISTORY' do
|
|
52
|
+
max = Legion::Extensions::Agentic::Affect::Flow::Helpers::Constants::MAX_FLOW_HISTORY
|
|
53
|
+
(max + 10).times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
54
|
+
expect(detector.history.size).to eq(max)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'accepts modifiers' do
|
|
58
|
+
detector.update(challenge_input: 0.6, skill_input: 0.6, modifiers: { curiosity_active: true })
|
|
59
|
+
expect(detector.flow_score).to be >= 0.0
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe 'flow state detection' do
|
|
64
|
+
def push_to_flow(count = 30)
|
|
65
|
+
count.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'enters flow when challenge and skill are balanced and moderate' do
|
|
69
|
+
push_to_flow
|
|
70
|
+
expect(detector.flow_state).to eq(:flow)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'detects arousal when challenge is high and skill is moderate-low' do
|
|
74
|
+
30.times { detector.update(challenge_input: 0.9, skill_input: 0.4) }
|
|
75
|
+
expect(detector.flow_state).to eq(:arousal)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'detects boredom when challenge is low and skill is high' do
|
|
79
|
+
30.times { detector.update(challenge_input: 0.1, skill_input: 0.8) }
|
|
80
|
+
expect(detector.flow_state).to eq(:boredom)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'detects anxiety when challenge is very high and skill is very low' do
|
|
84
|
+
30.times { detector.update(challenge_input: 0.95, skill_input: 0.1) }
|
|
85
|
+
expect(detector.flow_state).to eq(:anxiety)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'detects apathy when both are very low' do
|
|
89
|
+
30.times { detector.update(challenge_input: 0.1, skill_input: 0.1) }
|
|
90
|
+
expect(detector.flow_state).to eq(:apathy)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe '#in_flow?' do
|
|
95
|
+
it 'returns true when in flow state' do
|
|
96
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
97
|
+
expect(detector.in_flow?).to be true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'returns false when not in flow' do
|
|
101
|
+
expect(detector.in_flow?).to be false
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe '#deep_flow?' do
|
|
106
|
+
it 'returns false before threshold' do
|
|
107
|
+
19.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
108
|
+
expect(detector.deep_flow?).to be false
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'returns true after consecutive flow ticks reach threshold' do
|
|
112
|
+
# Push challenge/skill into flow range quickly first
|
|
113
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
114
|
+
# Now the detector should be in flow, keep it there past threshold
|
|
115
|
+
expect(detector.consecutive_flow_ticks).to be >= 20
|
|
116
|
+
expect(detector.deep_flow?).to be true
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe '#flow_effects' do
|
|
121
|
+
it 'returns neutral effects when not in flow' do
|
|
122
|
+
effects = detector.flow_effects
|
|
123
|
+
expect(effects[:performance_boost]).to eq(1.0)
|
|
124
|
+
expect(effects[:creativity_boost]).to eq(1.0)
|
|
125
|
+
expect(effects[:fatigue_reduction]).to eq(1.0)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'returns enhanced effects when in flow' do
|
|
129
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
130
|
+
effects = detector.flow_effects
|
|
131
|
+
expect(effects[:performance_boost]).to be > 1.0
|
|
132
|
+
expect(effects[:creativity_boost]).to be > 1.0
|
|
133
|
+
expect(effects[:fatigue_reduction]).to be < 1.0
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'provides extra boosts in deep flow' do
|
|
137
|
+
40.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
138
|
+
effects = detector.flow_effects
|
|
139
|
+
base_perf = Legion::Extensions::Agentic::Affect::Flow::Helpers::Constants::FLOW_EFFECTS[:performance_boost]
|
|
140
|
+
expect(effects[:performance_boost]).to be > base_perf
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe '#challenge_skill_balance' do
|
|
145
|
+
it 'returns 0 when challenge equals skill' do
|
|
146
|
+
expect(detector.challenge_skill_balance).to eq(0.0)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'returns positive value when unbalanced' do
|
|
150
|
+
30.times { detector.update(challenge_input: 0.9, skill_input: 0.1) }
|
|
151
|
+
expect(detector.challenge_skill_balance).to be > 0.0
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe '#flow_trend' do
|
|
156
|
+
it 'returns insufficient_data with fewer than 5 entries' do
|
|
157
|
+
3.times { detector.update(challenge_input: 0.5, skill_input: 0.5) }
|
|
158
|
+
expect(detector.flow_trend).to eq(:insufficient_data)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'detects entering_flow when scores are increasing' do
|
|
162
|
+
5.times { detector.update(challenge_input: 0.2, skill_input: 0.2) }
|
|
163
|
+
10.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
164
|
+
trend = detector.flow_trend
|
|
165
|
+
expect(trend).to eq(:entering_flow).or eq(:stable)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'detects leaving_flow when scores are decreasing' do
|
|
169
|
+
20.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
170
|
+
10.times { detector.update(challenge_input: 0.1, skill_input: 0.9) }
|
|
171
|
+
trend = detector.flow_trend
|
|
172
|
+
expect(trend).to eq(:leaving_flow).or eq(:stable)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'returns stable when scores are consistent' do
|
|
176
|
+
20.times { detector.update(challenge_input: 0.5, skill_input: 0.5) }
|
|
177
|
+
expect(detector.flow_trend).to eq(:stable)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe '#flow_percentage' do
|
|
182
|
+
it 'returns 0.0 with empty history' do
|
|
183
|
+
expect(detector.flow_percentage).to eq(0.0)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'returns percentage of flow ticks' do
|
|
187
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
188
|
+
expect(detector.flow_percentage).to be > 0.0
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
describe '#to_h' do
|
|
193
|
+
it 'returns a complete hash' do
|
|
194
|
+
detector.update(challenge_input: 0.6, skill_input: 0.6)
|
|
195
|
+
h = detector.to_h
|
|
196
|
+
expect(h).to include(
|
|
197
|
+
:state, :score, :challenge, :skill, :balance,
|
|
198
|
+
:in_flow, :deep_flow, :consecutive_flow_ticks,
|
|
199
|
+
:total_flow_ticks, :flow_percentage, :trend, :effects
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'rounds numeric values' do
|
|
204
|
+
detector.update(challenge_input: 0.6, skill_input: 0.6)
|
|
205
|
+
h = detector.to_h
|
|
206
|
+
expect(h[:score].to_s.split('.').last.length).to be <= 3
|
|
207
|
+
expect(h[:challenge].to_s.split('.').last.length).to be <= 3
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
describe 'flow score computation' do
|
|
212
|
+
it 'gives higher scores when in flow with good balance' do
|
|
213
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
214
|
+
expect(detector.flow_score).to be > 0.7
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'gives lower scores when not in flow' do
|
|
218
|
+
30.times { detector.update(challenge_input: 0.1, skill_input: 0.9) }
|
|
219
|
+
expect(detector.flow_score).to be < 0.5
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'adds curiosity bonus' do
|
|
223
|
+
20.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
224
|
+
score_without = detector.flow_score
|
|
225
|
+
detector2 = described_class.new
|
|
226
|
+
20.times { detector2.update(challenge_input: 0.6, skill_input: 0.6, modifiers: { curiosity_active: true }) }
|
|
227
|
+
expect(detector2.flow_score).to be >= score_without
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'adds low error bonus' do
|
|
231
|
+
20.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
232
|
+
score_without = detector.flow_score
|
|
233
|
+
detector2 = described_class.new
|
|
234
|
+
20.times { detector2.update(challenge_input: 0.6, skill_input: 0.6, modifiers: { low_errors: true }) }
|
|
235
|
+
expect(detector2.flow_score).to be >= score_without
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'clamps score to 0.0..1.0' do
|
|
239
|
+
50.times do
|
|
240
|
+
detector.update(challenge_input: 0.6, skill_input: 0.6,
|
|
241
|
+
modifiers: { curiosity_active: true, low_errors: true })
|
|
242
|
+
end
|
|
243
|
+
expect(detector.flow_score).to be_between(0.0, 1.0)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
describe 'consecutive flow tick tracking' do
|
|
248
|
+
it 'increments when in flow' do
|
|
249
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
250
|
+
expect(detector.consecutive_flow_ticks).to be > 0
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it 'resets when leaving flow' do
|
|
254
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
255
|
+
expect(detector.consecutive_flow_ticks).to be > 0
|
|
256
|
+
30.times { detector.update(challenge_input: 0.1, skill_input: 0.9) }
|
|
257
|
+
expect(detector.consecutive_flow_ticks).to eq(0)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'accumulates total flow ticks across sessions' do
|
|
261
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
262
|
+
first_total = detector.total_flow_ticks
|
|
263
|
+
30.times { detector.update(challenge_input: 0.1, skill_input: 0.9) }
|
|
264
|
+
30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
|
|
265
|
+
expect(detector.total_flow_ticks).to be > first_total
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Agentic::Affect::Flow::Runners::Flow do
|
|
6
|
+
let(:client) { Legion::Extensions::Agentic::Affect::Flow::Client.new }
|
|
7
|
+
|
|
8
|
+
describe '#update_flow' do
|
|
9
|
+
let(:tick_results) do
|
|
10
|
+
{
|
|
11
|
+
prediction_engine: { rolling_accuracy: 0.6, error_rate: 0.3 },
|
|
12
|
+
action_selection: { complexity: 0.5 },
|
|
13
|
+
memory_retrieval: { avg_strength: 0.6 },
|
|
14
|
+
habit: { automation_level: 0.5 },
|
|
15
|
+
curiosity: { intensity: 0.3 },
|
|
16
|
+
emotional_evaluation: { anxiety: 0.2 }
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'returns flow state hash' do
|
|
21
|
+
result = client.update_flow(tick_results: tick_results)
|
|
22
|
+
expect(result).to include(:state, :score, :in_flow, :deep_flow, :effects, :breakers, :challenge, :skill)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'returns numeric score' do
|
|
26
|
+
result = client.update_flow(tick_results: tick_results)
|
|
27
|
+
expect(result[:score]).to be_a(Float)
|
|
28
|
+
expect(result[:score]).to be_between(0.0, 1.0)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'returns challenge and skill values' do
|
|
32
|
+
result = client.update_flow(tick_results: tick_results)
|
|
33
|
+
expect(result[:challenge]).to be_between(0.0, 1.0)
|
|
34
|
+
expect(result[:skill]).to be_between(0.0, 1.0)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'returns empty breakers for normal tick' do
|
|
38
|
+
result = client.update_flow(tick_results: tick_results)
|
|
39
|
+
expect(result[:breakers]).to be_empty
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'returns effects hash' do
|
|
43
|
+
result = client.update_flow(tick_results: tick_results)
|
|
44
|
+
expect(result[:effects]).to be_a(Hash)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'with flow breakers' do
|
|
48
|
+
it 'detects high anxiety' do
|
|
49
|
+
tick_results[:emotional_evaluation][:anxiety] = 0.9
|
|
50
|
+
# Push into flow first
|
|
51
|
+
20.times { client.update_flow(tick_results: tick_results.merge(emotional_evaluation: { anxiety: 0.1 })) }
|
|
52
|
+
result = client.update_flow(tick_results: tick_results)
|
|
53
|
+
expect(result[:breakers]).to include(:high_anxiety)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'detects trust violation' do
|
|
57
|
+
tick_results[:trust] = { violation: true }
|
|
58
|
+
result = client.update_flow(tick_results: tick_results)
|
|
59
|
+
expect(result[:breakers]).to include(:trust_violation)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'detects critical error' do
|
|
63
|
+
tick_results[:error] = { critical: true }
|
|
64
|
+
result = client.update_flow(tick_results: tick_results)
|
|
65
|
+
expect(result[:breakers]).to include(:critical_error)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'detects burnout' do
|
|
69
|
+
tick_results[:fatigue] = { burnout: true }
|
|
70
|
+
result = client.update_flow(tick_results: tick_results)
|
|
71
|
+
expect(result[:breakers]).to include(:burnout)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'detects conflict escalation' do
|
|
75
|
+
tick_results[:conflict] = { severity: 4 }
|
|
76
|
+
result = client.update_flow(tick_results: tick_results)
|
|
77
|
+
expect(result[:breakers]).to include(:conflict_escalation)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context 'with default tick_results' do
|
|
82
|
+
it 'handles empty tick_results' do
|
|
83
|
+
result = client.update_flow(tick_results: {})
|
|
84
|
+
expect(result[:state]).to be_a(Symbol)
|
|
85
|
+
expect(result[:score]).to be_a(Float)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '#flow_status' do
|
|
91
|
+
it 'returns full status hash' do
|
|
92
|
+
status = client.flow_status
|
|
93
|
+
expect(status).to include(
|
|
94
|
+
:state, :score, :challenge, :skill, :balance,
|
|
95
|
+
:in_flow, :deep_flow, :consecutive_flow_ticks,
|
|
96
|
+
:total_flow_ticks, :flow_percentage, :trend, :effects
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe '#flow_effects' do
|
|
102
|
+
it 'returns effects with flow info' do
|
|
103
|
+
result = client.flow_effects
|
|
104
|
+
expect(result).to include(:effects, :in_flow, :deep_flow)
|
|
105
|
+
expect(result[:effects]).to be_a(Hash)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'returns neutral effects initially' do
|
|
109
|
+
result = client.flow_effects
|
|
110
|
+
expect(result[:effects][:performance_boost]).to eq(1.0)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe '#flow_history' do
|
|
115
|
+
it 'returns empty history initially' do
|
|
116
|
+
result = client.flow_history
|
|
117
|
+
expect(result[:history]).to be_empty
|
|
118
|
+
expect(result[:total]).to eq(0)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'returns history after updates' do
|
|
122
|
+
5.times { client.update_flow(tick_results: {}) }
|
|
123
|
+
result = client.flow_history
|
|
124
|
+
expect(result[:history].size).to eq(5)
|
|
125
|
+
expect(result[:total]).to eq(5)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'respects limit parameter' do
|
|
129
|
+
10.times { client.update_flow(tick_results: {}) }
|
|
130
|
+
result = client.flow_history(limit: 3)
|
|
131
|
+
expect(result[:history].size).to eq(3)
|
|
132
|
+
expect(result[:total]).to eq(10)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe '#flow_stats' do
|
|
137
|
+
it 'returns stats hash' do
|
|
138
|
+
stats = client.flow_stats
|
|
139
|
+
expect(stats).to include(:state, :score, :consecutive_flow_ticks, :total_flow_ticks,
|
|
140
|
+
:flow_percentage, :trend, :balance)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'returns numeric values' do
|
|
144
|
+
stats = client.flow_stats
|
|
145
|
+
expect(stats[:score]).to be_a(Float)
|
|
146
|
+
expect(stats[:flow_percentage]).to be_a(Float)
|
|
147
|
+
expect(stats[:balance]).to be_a(Float)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe 'challenge/skill extraction' do
|
|
152
|
+
it 'derives challenge from prediction accuracy, error rate, and complexity' do
|
|
153
|
+
high_challenge = {
|
|
154
|
+
prediction_engine: { rolling_accuracy: 0.2, error_rate: 0.8 },
|
|
155
|
+
action_selection: { complexity: 0.9 }
|
|
156
|
+
}
|
|
157
|
+
low_challenge = {
|
|
158
|
+
prediction_engine: { rolling_accuracy: 0.9, error_rate: 0.1 },
|
|
159
|
+
action_selection: { complexity: 0.1 }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
client_high = Legion::Extensions::Agentic::Affect::Flow::Client.new
|
|
163
|
+
client_low = Legion::Extensions::Agentic::Affect::Flow::Client.new
|
|
164
|
+
20.times do
|
|
165
|
+
client_high.update_flow(tick_results: high_challenge)
|
|
166
|
+
client_low.update_flow(tick_results: low_challenge)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
high_result = client_high.flow_status
|
|
170
|
+
low_result = client_low.flow_status
|
|
171
|
+
expect(high_result[:challenge]).to be > low_result[:challenge]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'derives skill from prediction accuracy, memory strength, and habit' do
|
|
175
|
+
high_skill = {
|
|
176
|
+
prediction_engine: { rolling_accuracy: 0.9 },
|
|
177
|
+
memory_retrieval: { avg_strength: 0.9 },
|
|
178
|
+
habit: { automation_level: 0.9 }
|
|
179
|
+
}
|
|
180
|
+
low_skill = {
|
|
181
|
+
prediction_engine: { rolling_accuracy: 0.1 },
|
|
182
|
+
memory_retrieval: { avg_strength: 0.1 },
|
|
183
|
+
habit: { automation_level: 0.1 }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
client_high = Legion::Extensions::Agentic::Affect::Flow::Client.new
|
|
187
|
+
client_low = Legion::Extensions::Agentic::Affect::Flow::Client.new
|
|
188
|
+
20.times do
|
|
189
|
+
client_high.update_flow(tick_results: high_skill)
|
|
190
|
+
client_low.update_flow(tick_results: low_skill)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
high_result = client_high.flow_status
|
|
194
|
+
low_result = client_low.flow_status
|
|
195
|
+
expect(high_result[:skill]).to be > low_result[:skill]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
describe 'flow progression over time' do
|
|
200
|
+
let(:balanced_tick) do
|
|
201
|
+
{
|
|
202
|
+
prediction_engine: { rolling_accuracy: 0.5, error_rate: 0.5 },
|
|
203
|
+
action_selection: { complexity: 0.7 },
|
|
204
|
+
memory_retrieval: { avg_strength: 0.7 },
|
|
205
|
+
habit: { automation_level: 0.7 },
|
|
206
|
+
curiosity: { intensity: 0.6 }
|
|
207
|
+
}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'reaches flow state with sustained balanced input' do
|
|
211
|
+
40.times { client.update_flow(tick_results: balanced_tick) }
|
|
212
|
+
expect(client.flow_status[:in_flow]).to be true
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'reaches deep flow after threshold' do
|
|
216
|
+
50.times { client.update_flow(tick_results: balanced_tick) }
|
|
217
|
+
status = client.flow_status
|
|
218
|
+
# May or may not reach deep flow depending on exact EMA convergence
|
|
219
|
+
expect(status[:consecutive_flow_ticks]).to be >= 0
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Agentic::Affect::Interoception::Client do
|
|
4
|
+
subject(:client) { described_class.new }
|
|
5
|
+
|
|
6
|
+
it 'includes Runners::Interoception' do
|
|
7
|
+
expect(described_class.ancestors).to include(Legion::Extensions::Agentic::Affect::Interoception::Runners::Interoception)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'responds to all runner methods' do
|
|
11
|
+
expect(client).to respond_to(:report_vital)
|
|
12
|
+
expect(client).to respond_to(:create_somatic_marker)
|
|
13
|
+
expect(client).to respond_to(:query_bias)
|
|
14
|
+
expect(client).to respond_to(:reinforce_somatic)
|
|
15
|
+
expect(client).to respond_to(:deviating_vitals)
|
|
16
|
+
expect(client).to respond_to(:body_status)
|
|
17
|
+
expect(client).to respond_to(:update_interoception)
|
|
18
|
+
expect(client).to respond_to(:interoception_stats)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'supports full somatic marker lifecycle' do
|
|
22
|
+
# Report some vitals
|
|
23
|
+
client.report_vital(channel: :cpu_load, value: 0.3)
|
|
24
|
+
client.report_vital(channel: :connection_health, value: 0.9)
|
|
25
|
+
|
|
26
|
+
# Create markers based on outcomes
|
|
27
|
+
client.create_somatic_marker(action: :deploy, domain: :prod, valence: 0.8)
|
|
28
|
+
client.create_somatic_marker(action: :risky_change, domain: :prod, valence: -0.6)
|
|
29
|
+
|
|
30
|
+
# Query bias should reflect markers
|
|
31
|
+
approach = client.query_bias(action: :deploy)
|
|
32
|
+
expect(approach[:bias]).to be > 0
|
|
33
|
+
|
|
34
|
+
avoid = client.query_bias(action: :risky_change)
|
|
35
|
+
expect(avoid[:bias]).to be < 0
|
|
36
|
+
|
|
37
|
+
# Reinforce good outcome
|
|
38
|
+
client.reinforce_somatic(action: :deploy)
|
|
39
|
+
|
|
40
|
+
# Tick decay
|
|
41
|
+
client.update_interoception
|
|
42
|
+
|
|
43
|
+
# Check overall status
|
|
44
|
+
status = client.body_status
|
|
45
|
+
expect(status[:success]).to be true
|
|
46
|
+
expect(status[:health]).to be > 0
|
|
47
|
+
|
|
48
|
+
stats = client.interoception_stats
|
|
49
|
+
expect(stats[:stats][:channels]).to eq(2)
|
|
50
|
+
expect(stats[:stats][:markers]).to be >= 1
|
|
51
|
+
end
|
|
52
|
+
end
|