lex-joint-attention 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/Gemfile +11 -0
- data/lex-joint-attention.gemspec +31 -0
- data/lib/legion/extensions/joint_attention/actors/decay.rb +41 -0
- data/lib/legion/extensions/joint_attention/client.rb +24 -0
- data/lib/legion/extensions/joint_attention/helpers/attention_target.rb +120 -0
- data/lib/legion/extensions/joint_attention/helpers/constants.rb +30 -0
- data/lib/legion/extensions/joint_attention/helpers/joint_focus_manager.rb +153 -0
- data/lib/legion/extensions/joint_attention/runners/joint_attention.rb +84 -0
- data/lib/legion/extensions/joint_attention/version.rb +9 -0
- data/lib/legion/extensions/joint_attention.rb +17 -0
- data/spec/legion/extensions/joint_attention/client_spec.rb +36 -0
- data/spec/legion/extensions/joint_attention/helpers/attention_target_spec.rb +258 -0
- data/spec/legion/extensions/joint_attention/helpers/joint_focus_manager_spec.rb +238 -0
- data/spec/legion/extensions/joint_attention/runners/joint_attention_spec.rb +228 -0
- data/spec/spec_helper.rb +20 -0
- metadata +77 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::JointAttention::Runners::JointAttention do
|
|
4
|
+
let(:client) { Legion::Extensions::JointAttention::Client.new }
|
|
5
|
+
|
|
6
|
+
let!(:target_result) do
|
|
7
|
+
client.create_attention_target(name: 'shared-task', domain: :work, priority: 0.7, creator: 'agent-1')
|
|
8
|
+
end
|
|
9
|
+
let(:target_id) { target_result[:target][:id] }
|
|
10
|
+
|
|
11
|
+
describe '#create_attention_target' do
|
|
12
|
+
it 'returns success: true' do
|
|
13
|
+
expect(target_result[:success]).to be true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'returns target with all fields' do
|
|
17
|
+
t = target_result[:target]
|
|
18
|
+
expect(t).to include(:id, :name, :domain, :priority, :creator_agent_id, :created_at, :attendee_count)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'sets the name correctly' do
|
|
22
|
+
expect(target_result[:target][:name]).to eq('shared-task')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'uses default priority when not specified' do
|
|
26
|
+
result = client.create_attention_target(name: 'default-prio', domain: :x, creator: 'a')
|
|
27
|
+
const = Legion::Extensions::JointAttention::Helpers::Constants::DEFAULT_FOCUS
|
|
28
|
+
expect(result[:target][:priority]).to be_within(0.01).of(const)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'auto-joins the creator' do
|
|
32
|
+
expect(target_result[:target][:attendee_count]).to eq(1)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '#join_attention' do
|
|
37
|
+
it 'joins a second agent and returns success' do
|
|
38
|
+
result = client.join_attention(target_id: target_id, agent_id: 'agent-2')
|
|
39
|
+
expect(result[:success]).to be true
|
|
40
|
+
expect(result[:result]).to eq(:joined)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'returns success for already_attending' do
|
|
44
|
+
client.join_attention(target_id: target_id, agent_id: 'agent-2')
|
|
45
|
+
result = client.join_attention(target_id: target_id, agent_id: 'agent-2')
|
|
46
|
+
expect(result[:success]).to be true
|
|
47
|
+
expect(result[:result]).to eq(:already_attending)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'returns success: false for unknown target' do
|
|
51
|
+
result = client.join_attention(target_id: 'bogus', agent_id: 'agent-2')
|
|
52
|
+
expect(result[:success]).to be false
|
|
53
|
+
expect(result[:result]).to eq(:target_not_found)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'includes target_id and agent_id in response' do
|
|
57
|
+
result = client.join_attention(target_id: target_id, agent_id: 'agent-2')
|
|
58
|
+
expect(result[:target_id]).to eq(target_id)
|
|
59
|
+
expect(result[:agent_id]).to eq('agent-2')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'accepts a gaze keyword' do
|
|
63
|
+
result = client.join_attention(target_id: target_id, agent_id: 'agent-2', gaze: :deadline)
|
|
64
|
+
expect(result[:success]).to be true
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#leave_attention' do
|
|
69
|
+
before { client.join_attention(target_id: target_id, agent_id: 'agent-2') }
|
|
70
|
+
|
|
71
|
+
it 'removes the agent and returns success' do
|
|
72
|
+
result = client.leave_attention(target_id: target_id, agent_id: 'agent-2')
|
|
73
|
+
expect(result[:success]).to be true
|
|
74
|
+
expect(result[:result]).to eq(:removed)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'returns success: false for not_found' do
|
|
78
|
+
result = client.leave_attention(target_id: target_id, agent_id: 'never-joined')
|
|
79
|
+
expect(result[:success]).to be false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'returns success: false for unknown target' do
|
|
83
|
+
result = client.leave_attention(target_id: 'bogus', agent_id: 'agent-2')
|
|
84
|
+
expect(result[:success]).to be false
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe '#direct_attention' do
|
|
89
|
+
it 'directs to_agent to target and returns success' do
|
|
90
|
+
result = client.direct_attention(from_agent: 'agent-1', to_agent: 'agent-2', target_id: target_id)
|
|
91
|
+
expect(result[:success]).to be true
|
|
92
|
+
expect(result[:result]).to eq(:directed)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'returns success: false for unknown target' do
|
|
96
|
+
result = client.direct_attention(from_agent: 'agent-1', to_agent: 'agent-2', target_id: 'bogus')
|
|
97
|
+
expect(result[:success]).to be false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'returns success: false when referrer is not attending' do
|
|
101
|
+
result = client.direct_attention(from_agent: 'nobody', to_agent: 'agent-2', target_id: target_id)
|
|
102
|
+
expect(result[:success]).to be false
|
|
103
|
+
expect(result[:result]).to eq(:referrer_not_attending)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'includes from_agent and to_agent in response' do
|
|
107
|
+
result = client.direct_attention(from_agent: 'agent-1', to_agent: 'agent-2', target_id: target_id)
|
|
108
|
+
expect(result[:from_agent]).to eq('agent-1')
|
|
109
|
+
expect(result[:to_agent]).to eq('agent-2')
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe '#establish_mutual_awareness' do
|
|
114
|
+
before { client.join_attention(target_id: target_id, agent_id: 'agent-2') }
|
|
115
|
+
|
|
116
|
+
it 'establishes shared awareness and returns success' do
|
|
117
|
+
result = client.establish_mutual_awareness(target_id: target_id, agent_a: 'agent-1', agent_b: 'agent-2')
|
|
118
|
+
expect(result[:success]).to be true
|
|
119
|
+
expect(result[:result]).to eq(:established)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'returns success: false for unknown target' do
|
|
123
|
+
result = client.establish_mutual_awareness(target_id: 'bogus', agent_a: 'agent-1', agent_b: 'agent-2')
|
|
124
|
+
expect(result[:success]).to be false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'returns success: false when one agent is not attending' do
|
|
128
|
+
result = client.establish_mutual_awareness(target_id: target_id, agent_a: 'agent-1', agent_b: 'stranger')
|
|
129
|
+
expect(result[:success]).to be false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'includes agent_a and agent_b in response' do
|
|
133
|
+
result = client.establish_mutual_awareness(target_id: target_id, agent_a: 'agent-1', agent_b: 'agent-2')
|
|
134
|
+
expect(result[:agent_a]).to eq('agent-1')
|
|
135
|
+
expect(result[:agent_b]).to eq('agent-2')
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe '#update_gaze' do
|
|
140
|
+
it 'updates gaze direction and returns success' do
|
|
141
|
+
result = client.update_gaze(target_id: target_id, agent_id: 'agent-1', gaze: :risk)
|
|
142
|
+
expect(result[:success]).to be true
|
|
143
|
+
expect(result[:result]).to eq(:updated)
|
|
144
|
+
expect(result[:gaze]).to eq(:risk)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'returns success: false for unknown target' do
|
|
148
|
+
result = client.update_gaze(target_id: 'bogus', agent_id: 'agent-1', gaze: :risk)
|
|
149
|
+
expect(result[:success]).to be false
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'returns success: false for unknown agent' do
|
|
153
|
+
result = client.update_gaze(target_id: target_id, agent_id: 'nobody', gaze: :risk)
|
|
154
|
+
expect(result[:success]).to be false
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
describe '#shared_focus' do
|
|
159
|
+
it 'returns shared targets between two agents' do
|
|
160
|
+
client.join_attention(target_id: target_id, agent_id: 'agent-2')
|
|
161
|
+
result = client.shared_focus(agent_a: 'agent-1', agent_b: 'agent-2')
|
|
162
|
+
expect(result[:success]).to be true
|
|
163
|
+
expect(result[:count]).to eq(1)
|
|
164
|
+
expect(result[:shared_targets].first[:id]).to eq(target_id)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'returns empty when no shared targets' do
|
|
168
|
+
result = client.shared_focus(agent_a: 'agent-1', agent_b: 'loner')
|
|
169
|
+
expect(result[:success]).to be true
|
|
170
|
+
expect(result[:count]).to eq(0)
|
|
171
|
+
expect(result[:shared_targets]).to be_empty
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'includes agent_a and agent_b in response' do
|
|
175
|
+
result = client.shared_focus(agent_a: 'agent-1', agent_b: 'agent-2')
|
|
176
|
+
expect(result[:agent_a]).to eq('agent-1')
|
|
177
|
+
expect(result[:agent_b]).to eq('agent-2')
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe '#attention_targets_for' do
|
|
182
|
+
it 'returns targets for an agent' do
|
|
183
|
+
result = client.attention_targets_for(agent_id: 'agent-1')
|
|
184
|
+
expect(result[:success]).to be true
|
|
185
|
+
expect(result[:count]).to eq(1)
|
|
186
|
+
expect(result[:targets].first[:id]).to eq(target_id)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'returns empty for unknown agent' do
|
|
190
|
+
result = client.attention_targets_for(agent_id: 'nobody')
|
|
191
|
+
expect(result[:success]).to be true
|
|
192
|
+
expect(result[:count]).to eq(0)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'includes agent_id in response' do
|
|
196
|
+
result = client.attention_targets_for(agent_id: 'agent-1')
|
|
197
|
+
expect(result[:agent_id]).to eq('agent-1')
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
describe '#update_joint_attention' do
|
|
202
|
+
it 'returns success with stats' do
|
|
203
|
+
result = client.update_joint_attention
|
|
204
|
+
expect(result[:success]).to be true
|
|
205
|
+
expect(result).to have_key(:targets)
|
|
206
|
+
expect(result).to have_key(:agents)
|
|
207
|
+
expect(result).to have_key(:history)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'reflects current target count' do
|
|
211
|
+
result = client.update_joint_attention
|
|
212
|
+
expect(result[:targets]).to be >= 1
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
describe '#joint_attention_stats' do
|
|
217
|
+
it 'returns success with full stats hash' do
|
|
218
|
+
result = client.joint_attention_stats
|
|
219
|
+
expect(result[:success]).to be true
|
|
220
|
+
expect(result[:stats]).to include(:target_count, :agent_count, :history_size, :targets)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'stats reflect created targets' do
|
|
224
|
+
stats = client.joint_attention_stats
|
|
225
|
+
expect(stats[:stats][:target_count]).to eq(1)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Logging
|
|
7
|
+
def self.debug(_msg); end
|
|
8
|
+
def self.info(_msg); end
|
|
9
|
+
def self.warn(_msg); end
|
|
10
|
+
def self.error(_msg); end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require 'legion/extensions/joint_attention'
|
|
15
|
+
|
|
16
|
+
RSpec.configure do |config|
|
|
17
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
|
18
|
+
config.disable_monkey_patching!
|
|
19
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-joint-attention
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Tomasello's joint attention framework for LegionIO — shared focus between
|
|
27
|
+
agents, mutual awareness tracking, referential communication, and collaborative
|
|
28
|
+
attention management with focus decay and working-memory constraints.
|
|
29
|
+
email:
|
|
30
|
+
- matthewdiverson@gmail.com
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- Gemfile
|
|
36
|
+
- lex-joint-attention.gemspec
|
|
37
|
+
- lib/legion/extensions/joint_attention.rb
|
|
38
|
+
- lib/legion/extensions/joint_attention/actors/decay.rb
|
|
39
|
+
- lib/legion/extensions/joint_attention/client.rb
|
|
40
|
+
- lib/legion/extensions/joint_attention/helpers/attention_target.rb
|
|
41
|
+
- lib/legion/extensions/joint_attention/helpers/constants.rb
|
|
42
|
+
- lib/legion/extensions/joint_attention/helpers/joint_focus_manager.rb
|
|
43
|
+
- lib/legion/extensions/joint_attention/runners/joint_attention.rb
|
|
44
|
+
- lib/legion/extensions/joint_attention/version.rb
|
|
45
|
+
- spec/legion/extensions/joint_attention/client_spec.rb
|
|
46
|
+
- spec/legion/extensions/joint_attention/helpers/attention_target_spec.rb
|
|
47
|
+
- spec/legion/extensions/joint_attention/helpers/joint_focus_manager_spec.rb
|
|
48
|
+
- spec/legion/extensions/joint_attention/runners/joint_attention_spec.rb
|
|
49
|
+
- spec/spec_helper.rb
|
|
50
|
+
homepage: https://github.com/LegionIO/lex-joint-attention
|
|
51
|
+
licenses:
|
|
52
|
+
- MIT
|
|
53
|
+
metadata:
|
|
54
|
+
homepage_uri: https://github.com/LegionIO/lex-joint-attention
|
|
55
|
+
source_code_uri: https://github.com/LegionIO/lex-joint-attention
|
|
56
|
+
documentation_uri: https://github.com/LegionIO/lex-joint-attention
|
|
57
|
+
changelog_uri: https://github.com/LegionIO/lex-joint-attention
|
|
58
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-joint-attention/issues
|
|
59
|
+
rubygems_mfa_required: 'true'
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.4'
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 3.6.9
|
|
75
|
+
specification_version: 4
|
|
76
|
+
summary: LEX Joint Attention
|
|
77
|
+
test_files: []
|