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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 650958df48e1ad4fa390b3fecb3ab2817dc535c279449dc4ce114e92f59cfff2
4
- data.tar.gz: ff13ef5a0e18147daa15b72579de24f3691bec50f5f124627d2b503208cfba82
3
+ metadata.gz: d736de5644beff024c6acc40ededd1eface48a64dc919b4f1033e0971a7af71f
4
+ data.tar.gz: e58b0661aab721ebaf1c7f413691a41f2a5f0f1e455710b3d27f79ad687655fb
5
5
  SHA512:
6
- metadata.gz: fc982147fb2a22e4a5bb40fa8f7a767e87aed754f59474d4937059a831adcfeae6b4f4e4c9add47359c3b6a69200a0cdea21c1bafd1b00dfd74501c93415eb40
7
- data.tar.gz: e34a1a0d9ae8481bb40949bda0e47d6e1647856777214d133acf10c121b8e70962bb85c10fda16120b9dd7bc958828fc20a73851ab259c89fa813d7df2236f26
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
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Mesh
6
- VERSION = '0.2.5'
6
+ VERSION = '0.3.0'
7
7
  end
8
8
  end
9
9
  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.2.5
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