lex-mesh 0.2.5 → 0.3.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 +4 -4
- data/lib/legion/extensions/mesh/client.rb +4 -0
- data/lib/legion/extensions/mesh/runners/task_request.rb +88 -0
- data/lib/legion/extensions/mesh/version.rb +1 -1
- data/lib/legion/extensions/mesh.rb +1 -0
- data/spec/legion/extensions/mesh/client_spec.rb +22 -0
- data/spec/legion/extensions/mesh/runners/task_request_spec.rb +91 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d736de5644beff024c6acc40ededd1eface48a64dc919b4f1033e0971a7af71f
|
|
4
|
+
data.tar.gz: e58b0661aab721ebaf1c7f413691a41f2a5f0f1e455710b3d27f79ad687655fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1abfff1fe01a29d3b9be8ee75c5f949f753a9fdf33df340e1362ef758236fb411a826eb0ae3c448bc330b8bc06cff725f35d10b5767f39ca7a8ffcb06969782
|
|
7
|
+
data.tar.gz: e1266fc12dd23f183439f2ed5c1d2a21a1c11b824652e918b6fc398df40eea46cc5aa0dbabbb14e99b8f41aa4b0cd2e99d71a259aaba8de595088c51336c7a62
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/mesh/runners/mesh'
|
|
4
4
|
require 'legion/extensions/mesh/runners/preferences'
|
|
5
|
+
require 'legion/extensions/mesh/runners/delegation'
|
|
6
|
+
require 'legion/extensions/mesh/runners/task_request'
|
|
5
7
|
require 'legion/extensions/mesh/helpers/preference_profile'
|
|
6
8
|
require 'legion/extensions/mesh/helpers/pending_requests'
|
|
7
9
|
require 'legion/extensions/mesh/helpers/topology'
|
|
@@ -13,6 +15,8 @@ module Legion
|
|
|
13
15
|
class Client
|
|
14
16
|
include Runners::Mesh
|
|
15
17
|
include Runners::Preferences
|
|
18
|
+
include Runners::Delegation
|
|
19
|
+
include Runners::TaskRequest
|
|
16
20
|
|
|
17
21
|
def initialize(**opts)
|
|
18
22
|
@opts = opts
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require_relative '../helpers/pending_requests'
|
|
5
|
+
require_relative '../helpers/delegation'
|
|
6
|
+
require_relative '../helpers/registry'
|
|
7
|
+
require_relative 'mesh'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module Mesh
|
|
12
|
+
module Runners
|
|
13
|
+
module TaskRequest
|
|
14
|
+
include Runners::Mesh
|
|
15
|
+
|
|
16
|
+
DEFAULT_TIMEOUT = 30
|
|
17
|
+
|
|
18
|
+
def request_task(from:, to:, task:, payload:, timeout: DEFAULT_TIMEOUT, consent_level: :execute, **) # rubocop:disable Metrics/ParameterLists
|
|
19
|
+
target_id = resolve_target(to)
|
|
20
|
+
return { success: false, reason: :no_agent_found, requested: to } unless target_id
|
|
21
|
+
|
|
22
|
+
correlation_id = "task-#{SecureRandom.uuid}"
|
|
23
|
+
|
|
24
|
+
delegation = delegation_tracker.create(
|
|
25
|
+
from: from, to: target_id,
|
|
26
|
+
task_context: task, consent_level: consent_level
|
|
27
|
+
)
|
|
28
|
+
return { success: false, reason: delegation[:error] } if delegation[:error]
|
|
29
|
+
|
|
30
|
+
task_pending.register(
|
|
31
|
+
correlation_id: correlation_id,
|
|
32
|
+
callback: nil,
|
|
33
|
+
ttl: timeout
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
send_message(from: from, to: target_id, pattern: :unicast,
|
|
37
|
+
payload: { type: :task_request, correlation_id: correlation_id,
|
|
38
|
+
task: task, payload: payload, reply_to: from })
|
|
39
|
+
|
|
40
|
+
Legion::Logging.info "[mesh-task] request: from=#{from} to=#{target_id} task=#{task} cid=#{correlation_id[0..11]}"
|
|
41
|
+
{ success: true, correlation_id: correlation_id, delegation_id: delegation[:delegation_id],
|
|
42
|
+
target_agent: target_id }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def handle_task_reply(correlation_id:, result:, **)
|
|
46
|
+
resolved = task_pending.resolve(correlation_id: correlation_id, result: result)
|
|
47
|
+
if resolved
|
|
48
|
+
Legion::Logging.debug "[mesh-task] reply resolved: cid=#{correlation_id[0..11]}"
|
|
49
|
+
{ success: true, resolved: true, correlation_id: correlation_id }
|
|
50
|
+
else
|
|
51
|
+
{ success: false, reason: :not_found, correlation_id: correlation_id }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def pending_task_stats(**)
|
|
56
|
+
{ success: true, pending_count: task_pending.pending_count }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def expire_pending_tasks(**)
|
|
60
|
+
expired = task_pending.expire
|
|
61
|
+
Legion::Logging.debug "[mesh-task] expired #{expired.size} pending tasks" unless expired.empty?
|
|
62
|
+
{ success: true, expired_count: expired.size, expired_ids: expired }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def resolve_target(to)
|
|
68
|
+
return to if mesh_registry.agents.key?(to)
|
|
69
|
+
|
|
70
|
+
agents = mesh_registry.find_by_capability(to.to_sym)
|
|
71
|
+
return nil if agents.empty?
|
|
72
|
+
|
|
73
|
+
online = agents.select { |a| a[:status] == :online }
|
|
74
|
+
(online.empty? ? agents : online).sample[:agent_id]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def task_pending
|
|
78
|
+
@task_pending ||= Helpers::PendingRequests.new(default_ttl: DEFAULT_TIMEOUT)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def delegation_tracker
|
|
82
|
+
@delegation_tracker ||= Helpers::Delegation.new
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -10,6 +10,7 @@ require 'legion/extensions/mesh/helpers/peer_verify'
|
|
|
10
10
|
require 'legion/extensions/mesh/runners/mesh'
|
|
11
11
|
require 'legion/extensions/mesh/runners/preferences'
|
|
12
12
|
require 'legion/extensions/mesh/runners/delegation'
|
|
13
|
+
require 'legion/extensions/mesh/runners/task_request'
|
|
13
14
|
|
|
14
15
|
if defined?(Legion::Transport)
|
|
15
16
|
require 'legion/extensions/mesh/transport/messages/preference_query'
|
|
@@ -51,4 +51,26 @@ RSpec.describe Legion::Extensions::Mesh::Client do
|
|
|
51
51
|
expect(result[:expired]).to eq(0)
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
|
+
|
|
55
|
+
it 'responds to delegation runner methods' do
|
|
56
|
+
expect(client).to respond_to(:delegate)
|
|
57
|
+
expect(client).to respond_to(:complete_delegation)
|
|
58
|
+
expect(client).to respond_to(:revoke_delegation)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'responds to task request runner methods' do
|
|
62
|
+
expect(client).to respond_to(:request_task)
|
|
63
|
+
expect(client).to respond_to(:handle_task_reply)
|
|
64
|
+
expect(client).to respond_to(:pending_task_stats)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe '#request_task' do
|
|
68
|
+
it 'sends a task request via the client' do
|
|
69
|
+
client.register(agent_id: 'a', capabilities: [:orchestration])
|
|
70
|
+
client.register(agent_id: 'b', capabilities: [:review])
|
|
71
|
+
result = client.request_task(from: 'a', to: 'b', task: 'review', payload: {})
|
|
72
|
+
expect(result[:success]).to be true
|
|
73
|
+
expect(result[:target_agent]).to eq('b')
|
|
74
|
+
end
|
|
75
|
+
end
|
|
54
76
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'legion/extensions/mesh/runners/task_request'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Legion::Extensions::Mesh::Runners::TaskRequest do
|
|
7
|
+
subject(:runner) { Object.new.extend(described_class) }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
runner.instance_variable_set(:@mesh_registry, nil)
|
|
11
|
+
runner.instance_variable_set(:@task_pending, nil)
|
|
12
|
+
runner.instance_variable_set(:@delegation_tracker, nil)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#request_task' do
|
|
16
|
+
before do
|
|
17
|
+
runner.register(agent_id: 'requester', capabilities: [:orchestration])
|
|
18
|
+
runner.register(agent_id: 'worker-1', capabilities: [:code_review])
|
|
19
|
+
runner.register(agent_id: 'worker-2', capabilities: %i[code_review testing])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'sends a task request to a specific agent by ID' do
|
|
23
|
+
result = runner.request_task(from: 'requester', to: 'worker-1',
|
|
24
|
+
task: 'review_code', payload: { file: 'app.rb' })
|
|
25
|
+
expect(result[:success]).to be true
|
|
26
|
+
expect(result[:correlation_id]).to be_a(String)
|
|
27
|
+
expect(result[:delegation_id]).to start_with('del-')
|
|
28
|
+
expect(result[:target_agent]).to eq('worker-1')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'routes to an agent by capability when to: is not a registered agent' do
|
|
32
|
+
result = runner.request_task(from: 'requester', to: 'code_review',
|
|
33
|
+
task: 'review_code', payload: {})
|
|
34
|
+
expect(result[:success]).to be true
|
|
35
|
+
expect(%w[worker-1 worker-2]).to include(result[:target_agent])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'returns error when no agent found for capability' do
|
|
39
|
+
result = runner.request_task(from: 'requester', to: 'nonexistent_capability',
|
|
40
|
+
task: 'do_thing', payload: {})
|
|
41
|
+
expect(result[:success]).to be false
|
|
42
|
+
expect(result[:reason]).to eq(:no_agent_found)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'registers a pending request' do
|
|
46
|
+
result = runner.request_task(from: 'requester', to: 'worker-1',
|
|
47
|
+
task: 'review', payload: {}, timeout: 60)
|
|
48
|
+
expect(runner.send(:task_pending).pending?(result[:correlation_id])).to be true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#handle_task_reply' do
|
|
53
|
+
it 'resolves a pending request' do
|
|
54
|
+
runner.register(agent_id: 'req', capabilities: [])
|
|
55
|
+
runner.register(agent_id: 'wrk', capabilities: [:work])
|
|
56
|
+
|
|
57
|
+
req = runner.request_task(from: 'req', to: 'wrk', task: 'test', payload: {})
|
|
58
|
+
result = runner.handle_task_reply(
|
|
59
|
+
correlation_id: req[:correlation_id],
|
|
60
|
+
result: { status: :completed, output: 'done' }
|
|
61
|
+
)
|
|
62
|
+
expect(result[:success]).to be true
|
|
63
|
+
expect(result[:resolved]).to be true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'returns not_found for unknown correlation_id' do
|
|
67
|
+
result = runner.handle_task_reply(correlation_id: 'unknown', result: {})
|
|
68
|
+
expect(result[:success]).to be false
|
|
69
|
+
expect(result[:reason]).to eq(:not_found)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#pending_task_stats' do
|
|
74
|
+
it 'returns count of pending requests' do
|
|
75
|
+
runner.register(agent_id: 'a', capabilities: [])
|
|
76
|
+
runner.register(agent_id: 'b', capabilities: [:work])
|
|
77
|
+
runner.request_task(from: 'a', to: 'b', task: 't', payload: {})
|
|
78
|
+
stats = runner.pending_task_stats
|
|
79
|
+
expect(stats[:success]).to be true
|
|
80
|
+
expect(stats[:pending_count]).to be >= 1
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe '#expire_pending_tasks' do
|
|
85
|
+
it 'expires timed-out requests' do
|
|
86
|
+
result = runner.expire_pending_tasks
|
|
87
|
+
expect(result[:success]).to be true
|
|
88
|
+
expect(result[:expired_count]).to eq(0)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-mesh
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -63,6 +63,7 @@ files:
|
|
|
63
63
|
- lib/legion/extensions/mesh/runners/delegation.rb
|
|
64
64
|
- lib/legion/extensions/mesh/runners/mesh.rb
|
|
65
65
|
- lib/legion/extensions/mesh/runners/preferences.rb
|
|
66
|
+
- lib/legion/extensions/mesh/runners/task_request.rb
|
|
66
67
|
- lib/legion/extensions/mesh/transport/messages/gossip.rb
|
|
67
68
|
- lib/legion/extensions/mesh/transport/messages/mesh_departure.rb
|
|
68
69
|
- lib/legion/extensions/mesh/transport/messages/preference_query.rb
|
|
@@ -86,6 +87,7 @@ files:
|
|
|
86
87
|
- spec/legion/extensions/mesh/runners/mesh_gossip_spec.rb
|
|
87
88
|
- spec/legion/extensions/mesh/runners/mesh_spec.rb
|
|
88
89
|
- spec/legion/extensions/mesh/runners/preferences_spec.rb
|
|
90
|
+
- spec/legion/extensions/mesh/runners/task_request_spec.rb
|
|
89
91
|
- spec/legion/extensions/mesh/transport/messages/gossip_spec.rb
|
|
90
92
|
- spec/legion/extensions/mesh/transport/messages/mesh_departure_spec.rb
|
|
91
93
|
- spec/legion/extensions/mesh/transport/messages/preference_query_spec.rb
|