agent99 0.0.1 → 0.0.3
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 +27 -1
- data/README.md +46 -2
- data/docs/README.md +57 -0
- data/docs/advanced_features.md +110 -0
- data/docs/agent_discovery.md +62 -0
- data/docs/agent_lifecycle.md +137 -0
- data/docs/agent_registry_processes.md +102 -0
- data/docs/api_reference.md +136 -0
- data/docs/architecture.md +77 -0
- data/docs/configuration.md +17 -0
- data/docs/control_actions.md +179 -0
- data/docs/custom_agent_implementation.md +30 -0
- data/docs/diagrams/agent_registry_processes.dot +42 -0
- data/docs/diagrams/agent_registry_processes.png +0 -0
- data/docs/diagrams/high_level_architecture.dot +26 -0
- data/docs/diagrams/high_level_architecture.png +0 -0
- data/docs/diagrams/request_flow.dot +42 -0
- data/docs/diagrams/request_flow.png +0 -0
- data/docs/error_handling_and_logging.md +13 -0
- data/docs/extending_the_framework.md +11 -0
- data/docs/message_processing.md +165 -0
- data/docs/messaging_system.md +129 -0
- data/docs/preformance_considerations.md +9 -0
- data/docs/schema_definition.md +78 -0
- data/docs/security.md +9 -0
- data/docs/troubleshooting.md +11 -0
- data/examples/README.md +65 -35
- data/examples/agent_watcher.rb +102 -0
- data/examples/agents/.keep +0 -0
- data/examples/chief_agent.rb +96 -0
- data/examples/control.rb +136 -0
- data/examples/diagram.dot +22 -0
- data/examples/diagram.png +0 -0
- data/examples/example_agent.rb +26 -0
- data/examples/kaos_spy.rb +63 -0
- data/examples/{hello_world.rb → maxwell_agent86.rb} +38 -18
- data/examples/{hello_world_request.rb → maxwell_request.rb} +2 -2
- data/examples/registry.rb +8 -7
- data/examples/start_rabbitmq_and_registry.sh +29 -0
- data/lib/agent99/agent_lifecycle.rb +14 -15
- data/lib/agent99/amqp_message_client.rb +41 -10
- data/lib/agent99/base.rb +6 -8
- data/lib/agent99/control_actions.rb +80 -47
- data/lib/agent99/header_management.rb +6 -4
- data/lib/agent99/header_schema.rb +5 -0
- data/lib/agent99/message_processing.rb +21 -13
- data/lib/agent99/registry_client.rb +8 -0
- data/lib/agent99/version.rb +1 -1
- data/lib/agent99.rb +4 -0
- metadata +41 -11
- data/docs/todo.md +0 -66
- data/examples/hello_world_client.rb +0 -70
- data/examples/start_agents.sh +0 -20
@@ -0,0 +1,96 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/chief_agent.rb
|
3
|
+
#
|
4
|
+
# This program is a type :client agent.
|
5
|
+
#
|
6
|
+
# This program calls a CLI program named boxes to highlight
|
7
|
+
# the response received to the request that it sent.
|
8
|
+
#
|
9
|
+
# brew install boxes
|
10
|
+
#
|
11
|
+
# Run this program several times to see if Maxwell Agent86
|
12
|
+
# messes up his mission which he is prone to do half the time.
|
13
|
+
|
14
|
+
require_relative '../lib/agent99'
|
15
|
+
|
16
|
+
class ChiefAgent < Agent99::Base
|
17
|
+
TYPE = :client
|
18
|
+
|
19
|
+
# init is called at the end of the initialization process.
|
20
|
+
# It may be only something that a :client type agent would do.
|
21
|
+
#
|
22
|
+
# For this client it sends out a request as its first order of
|
23
|
+
# business and expects to receive a response.
|
24
|
+
#
|
25
|
+
def init
|
26
|
+
action = 'greeter'
|
27
|
+
agent = discover_agent(
|
28
|
+
capability: action,
|
29
|
+
how_many: 1
|
30
|
+
).first[:uuid]
|
31
|
+
|
32
|
+
send_request(agent:)
|
33
|
+
|
34
|
+
rescue Exception => e
|
35
|
+
logger.warn "No Agents are available as #{action}"
|
36
|
+
exit(1)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
##################################################
|
41
|
+
private
|
42
|
+
|
43
|
+
def send_request(agent:)
|
44
|
+
request = build_request(
|
45
|
+
to_uuid: agent,
|
46
|
+
greeting: 'Hey',
|
47
|
+
name: 'MadBomber'
|
48
|
+
)
|
49
|
+
|
50
|
+
result = @message_client.publish(request)
|
51
|
+
logger.info "Sent request: #{request.inspect}; status? #{result.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def build_request(
|
56
|
+
to_uuid:,
|
57
|
+
greeting: 'Hello',
|
58
|
+
name: 'World'
|
59
|
+
)
|
60
|
+
|
61
|
+
{
|
62
|
+
header: {
|
63
|
+
type: 'request',
|
64
|
+
from_uuid: @id,
|
65
|
+
to_uuid: ,
|
66
|
+
event_uuid: SecureRandom.uuid,
|
67
|
+
timestamp: Agent99::Timestamp.new.to_i
|
68
|
+
},
|
69
|
+
greeting:,
|
70
|
+
name:
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def receive_response
|
76
|
+
logger.info "Received response: #{payload.inspect}"
|
77
|
+
result = payload[:result]
|
78
|
+
|
79
|
+
puts
|
80
|
+
puts `echo "#{result}" | boxes -d info`
|
81
|
+
puts
|
82
|
+
|
83
|
+
exit(0)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def capabilities
|
88
|
+
['Chief of Control']
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Example usage
|
94
|
+
client = ChiefAgent.new
|
95
|
+
client.run
|
96
|
+
|
data/examples/control.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# control.rb
|
3
|
+
|
4
|
+
require_relative '../lib/agent99'
|
5
|
+
|
6
|
+
class Control < Agent99::Base
|
7
|
+
TYPE = :hybrid
|
8
|
+
|
9
|
+
attr_accessor :statuses
|
10
|
+
|
11
|
+
def init
|
12
|
+
@agents = @registry_client.fetch_all_agents
|
13
|
+
@statuses = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def capabilities
|
18
|
+
['control', 'headquarters', 'secret underground base']
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def send_control_message(message:, payload: {})
|
23
|
+
@agents.each do |agent|
|
24
|
+
response = @message_client.publish(
|
25
|
+
header: {
|
26
|
+
to_uuid: agent[:uuid],
|
27
|
+
from_uuid: @id,
|
28
|
+
event_uuid: SecureRandom.uuid,
|
29
|
+
type: 'control',
|
30
|
+
timestamp: Agent99::Timestamp.new.to_i
|
31
|
+
},
|
32
|
+
action: message,
|
33
|
+
payload: payload
|
34
|
+
)
|
35
|
+
puts "Sent #{message} to #{agent[:name]}: #{response[:success] ? 'Success' : 'Failed'}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def pause_all
|
41
|
+
send_control_message(message: 'pause')
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def resume_all
|
46
|
+
send_control_message(message: 'resume')
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def stop_all
|
51
|
+
send_control_message(message: 'stop')
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_all_status
|
55
|
+
@statuses.clear # Reset statuses before new request
|
56
|
+
|
57
|
+
@agents.each do |agent|
|
58
|
+
@message_client.publish(
|
59
|
+
header: {
|
60
|
+
to_uuid: agent[:uuid],
|
61
|
+
from_uuid: @id,
|
62
|
+
event_uuid: SecureRandom.uuid,
|
63
|
+
type: 'control',
|
64
|
+
timestamp: Agent99::Timestamp.new.to_i
|
65
|
+
},
|
66
|
+
action: 'status'
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Wait for responses (with timeout)
|
71
|
+
sleep 2 # Give agents time to respond
|
72
|
+
@statuses
|
73
|
+
end
|
74
|
+
|
75
|
+
def receive_response
|
76
|
+
if payload[:action] == 'response' && payload[:data][:type] == 'status'
|
77
|
+
agent_name = payload[:header][:from_uuid]
|
78
|
+
@statuses[agent_name] = payload[:data]
|
79
|
+
logger.info "Received status from #{agent_name}: #{payload[:data]}"
|
80
|
+
elsif payload[:action] == 'status' && payload[:header][:from_uuid] == @id
|
81
|
+
# Handle our own status request
|
82
|
+
handle_status_request
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
if __FILE__ == $PROGRAM_NAME
|
91
|
+
control = Control.new
|
92
|
+
|
93
|
+
# Start the message processing in a separate thread
|
94
|
+
dispatcher_thread = Thread.new do
|
95
|
+
control.run
|
96
|
+
end
|
97
|
+
|
98
|
+
# UI thread
|
99
|
+
begin
|
100
|
+
loop do
|
101
|
+
puts "\n1. Pause all agents"
|
102
|
+
puts "2. Resume all agents"
|
103
|
+
puts "3. Stop all agents"
|
104
|
+
puts "4. Get all agents status"
|
105
|
+
puts "5. Exit"
|
106
|
+
print "\nEnter your choice: "
|
107
|
+
|
108
|
+
choice = gets.chomp.to_i
|
109
|
+
|
110
|
+
case choice
|
111
|
+
when 1
|
112
|
+
control.pause_all
|
113
|
+
when 2
|
114
|
+
control.resume_all
|
115
|
+
when 3
|
116
|
+
control.stop_all
|
117
|
+
when 4
|
118
|
+
statuses = control.get_all_status
|
119
|
+
sleep 2 # Give time for responses to arrive
|
120
|
+
puts JSON.pretty_generate(control.statuses)
|
121
|
+
when 5
|
122
|
+
puts "Exiting..."
|
123
|
+
Thread.exit
|
124
|
+
break
|
125
|
+
else
|
126
|
+
puts "Invalid choice. Please try again."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
rescue Interrupt
|
130
|
+
puts "\nShutting down..."
|
131
|
+
ensure
|
132
|
+
dispatcher_thread.exit
|
133
|
+
control.fini
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
digraph agent_interaction {
|
2
|
+
rankdir=LR; // Left to right layout
|
3
|
+
|
4
|
+
// Define nodes for each component
|
5
|
+
node [shape=box];
|
6
|
+
ChiefAgent [label="ChiefAgent"];
|
7
|
+
MaxwellAgent86 [label="MaxwellAgent86"];
|
8
|
+
Registry [label="Registry Service"];
|
9
|
+
|
10
|
+
// Define edges for interactions
|
11
|
+
ChiefAgent -> Registry [label="GET /discover (greeter)"];
|
12
|
+
Registry -> ChiefAgent [label="Agent UUIDs"];
|
13
|
+
|
14
|
+
ChiefAgent -> MaxwellAgent86 [label="Send greet request"];
|
15
|
+
MaxwellAgent86 -> ChiefAgent [label="Response (Hello World)"];
|
16
|
+
|
17
|
+
MaxwellAgent86 -> Registry [label="POST /register"];
|
18
|
+
Registry -> MaxwellAgent86 [label="ACK (registration)"];
|
19
|
+
|
20
|
+
ChiefAgent -> Registry [label="POST /register"];
|
21
|
+
Registry -> ChiefAgent [label="ACK (registration)"];
|
22
|
+
}
|
Binary file
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# examples/example_agent.rb
|
2
|
+
#
|
3
|
+
# NOTE: This agent is meant to be loaded
|
4
|
+
# by the agent_watcher.rb be file.
|
5
|
+
# To do that first have AgentWatcher running
|
6
|
+
# then `cp example_agent.rb agents`
|
7
|
+
# AgentWatcher will see the new file arrive
|
8
|
+
# in the `agents` folder, will determine that the
|
9
|
+
# new file contains an Agent99 subclass, will
|
10
|
+
# load it, create a new instance of the class and
|
11
|
+
# finally run the new instance within its own
|
12
|
+
# thread as part of the AgentWatcher process.
|
13
|
+
#
|
14
|
+
|
15
|
+
require_relative '../../lib/agent99'
|
16
|
+
|
17
|
+
class ExampleAgent < Agent99::Base
|
18
|
+
TYPE = :server
|
19
|
+
|
20
|
+
def capabilities = %w[ rubber_stamp yes_man example ]
|
21
|
+
|
22
|
+
def receive_request
|
23
|
+
logger.info "Example agent received request: #{payload}"
|
24
|
+
send_response(status: 'success')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/kaos_spy.rb
|
3
|
+
#
|
4
|
+
# KAOS stood for "Kreatively Akting Out Simultaneously."
|
5
|
+
|
6
|
+
require 'agent99'
|
7
|
+
|
8
|
+
# Kaos captured Agent99 and forced her to reveal the
|
9
|
+
# secrets of the centralized registry and the communication
|
10
|
+
# network used by Control. Max was not there to save her.
|
11
|
+
|
12
|
+
require 'tty-table'
|
13
|
+
|
14
|
+
class KaosSpy
|
15
|
+
# TODO: spread some choas!
|
16
|
+
|
17
|
+
attr_reader :registry, :comms, :agents
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@registry = Agent99::RegistryClient.new
|
21
|
+
@agents = registry.fetch_all_agents
|
22
|
+
dox_control_agents
|
23
|
+
|
24
|
+
@comms = Agent99::AmqpMessageClient.new
|
25
|
+
take_out_communications
|
26
|
+
end
|
27
|
+
|
28
|
+
def dox_control_agents
|
29
|
+
if agents.empty?
|
30
|
+
puts "\nKAOS won! There are no Control agents in the field."
|
31
|
+
else
|
32
|
+
report = [ %w[Name Address Capabilities] ]
|
33
|
+
|
34
|
+
agents.each{|agent|
|
35
|
+
report << [
|
36
|
+
agent[:name],
|
37
|
+
agent[:uuid],
|
38
|
+
agent[:capabilities].join(', ')
|
39
|
+
]
|
40
|
+
}
|
41
|
+
|
42
|
+
table = TTY::Table.new(report[0], report[1..])
|
43
|
+
puts table.render(:unicode)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def take_out_communications
|
48
|
+
puts
|
49
|
+
puts "Destroy Control's Comms Network ..."
|
50
|
+
puts
|
51
|
+
|
52
|
+
agents.each do |agent|
|
53
|
+
comms.delete_queue(agent[:uuid])
|
54
|
+
puts " Agent #{agent[:name]} cannot make or receive calls@"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
KaosSpy.new
|
60
|
+
puts
|
61
|
+
puts "That's all it takes - Get Smart; get security!"
|
62
|
+
puts
|
63
|
+
|
@@ -1,22 +1,31 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# examples/
|
2
|
+
# examples/maxwell_agent86.rb
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
# There are three types of agents: Server, Client and Hybrid.
|
5
|
+
# A Server receives a requests and _may_ send a response.
|
6
|
+
# A Client sends a request and _may_ expect a response.
|
7
|
+
# A Hybrid _may_ act like a Server or a Client
|
6
8
|
|
7
9
|
require_relative '../lib/agent99'
|
8
|
-
require_relative '
|
10
|
+
require_relative 'maxwell_request'
|
11
|
+
|
12
|
+
class MaxwellAgent86 < Agent99::Base
|
13
|
+
REQUEST_SCHEMA = MaxwellRequest.schema
|
14
|
+
TYPE = :server
|
9
15
|
|
10
|
-
class HelloWorld < Agent99::Base
|
11
|
-
REQUEST_SCHEMA = HelloWorldRequest.schema
|
12
16
|
# RESPONSE_SCHEMA = Agent99::RESPONSE.schema
|
13
17
|
# ERROR_SCHEMA = Agent99::ERROR.schema
|
14
18
|
|
19
|
+
|
20
|
+
#######################################
|
21
|
+
private
|
22
|
+
|
15
23
|
# The request is in @payload
|
16
24
|
def receive_request
|
17
25
|
send_response( validate_request || process )
|
18
26
|
end
|
19
27
|
|
28
|
+
|
20
29
|
# This method validates the incoming request and returns any errors found
|
21
30
|
# or nil if there are no errors.
|
22
31
|
# It allows for returning an array of errors.
|
@@ -44,6 +53,7 @@ class HelloWorld < Agent99::Base
|
|
44
53
|
}
|
45
54
|
end
|
46
55
|
|
56
|
+
|
47
57
|
# Validate the incoming request body against the schema
|
48
58
|
validation_errors = validate_schema
|
49
59
|
unless validation_errors.empty?
|
@@ -57,6 +67,7 @@ class HelloWorld < Agent99::Base
|
|
57
67
|
responses.empty? ? nil : responses
|
58
68
|
end
|
59
69
|
|
70
|
+
|
60
71
|
# Returns the response value
|
61
72
|
# All response message have the same schema in that
|
62
73
|
# they have a header (all messages have headers) and
|
@@ -64,9 +75,13 @@ class HelloWorld < Agent99::Base
|
|
64
75
|
# a JSON string, sure but then we would need a
|
65
76
|
# RESPONSE_SCHEMA constant for the class.
|
66
77
|
def process
|
67
|
-
|
68
|
-
|
69
|
-
|
78
|
+
result = if 50 <= rand(100)
|
79
|
+
get(:greeting) + ' ' + get(:name)
|
80
|
+
else
|
81
|
+
'Missed it by that >< much.'
|
82
|
+
end
|
83
|
+
|
84
|
+
{ result: result }
|
70
85
|
end
|
71
86
|
|
72
87
|
|
@@ -76,16 +91,21 @@ class HelloWorld < Agent99::Base
|
|
76
91
|
loger.warn("Unexpected response type message: response.inspect")
|
77
92
|
end
|
78
93
|
|
79
|
-
private
|
80
94
|
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
95
|
+
# Phase One Implementation is to do a search
|
96
|
+
# using the String#include? and the Array#include?
|
97
|
+
# methods. If you want discrete word-based selection
|
98
|
+
# then use an Array of Strings to define the different
|
99
|
+
# things this agent can do.
|
100
|
+
#
|
101
|
+
# If you want to match on sub-strings then define the
|
102
|
+
# the capabilities as a String.
|
103
|
+
#
|
104
|
+
# Subsequent implementations may use a semantic search
|
105
|
+
# to find the agents to use in which case capabilities may
|
106
|
+
# be constrained to be a String.
|
85
107
|
#
|
86
|
-
#
|
87
|
-
# until the registry program gets more
|
88
|
-
# stuff added to its discovery process.
|
108
|
+
# For now, lets just go with the Array of Strings.
|
89
109
|
#
|
90
110
|
def capabilities
|
91
111
|
%w[ greeter hello_world hello-world hello]
|
@@ -93,5 +113,5 @@ class HelloWorld < Agent99::Base
|
|
93
113
|
end
|
94
114
|
|
95
115
|
# Example usage
|
96
|
-
agent =
|
116
|
+
agent = MaxwellAgent86.new
|
97
117
|
agent.run # Starts listening for messages
|
@@ -1,8 +1,8 @@
|
|
1
|
-
# examples/
|
1
|
+
# examples/maxwell_request.rb
|
2
2
|
|
3
3
|
require_relative '../lib/agent99/header_schema'
|
4
4
|
|
5
|
-
class
|
5
|
+
class MaxwellRequest < SimpleJsonSchemaBuilder::Base
|
6
6
|
object do
|
7
7
|
object :header, schema: Agent99::HeaderSchema
|
8
8
|
|
data/examples/registry.rb
CHANGED
@@ -6,11 +6,13 @@ include DebugMe
|
|
6
6
|
|
7
7
|
require 'sinatra'
|
8
8
|
require 'json'
|
9
|
-
require 'bunny'
|
10
9
|
require 'securerandom'
|
11
10
|
|
12
|
-
# In-memory registry to store agent
|
13
|
-
#
|
11
|
+
# In-memory registry to store agent Array(Hash)
|
12
|
+
#
|
13
|
+
# Agent capabilities are save as lower case. The
|
14
|
+
# discovery process also compares content as lower case.
|
15
|
+
#
|
14
16
|
# TODO: change this data store to a sqlite database
|
15
17
|
# maybe with a vector search capability.
|
16
18
|
#
|
@@ -26,12 +28,11 @@ end
|
|
26
28
|
post '/register' do
|
27
29
|
request.body.rewind
|
28
30
|
agent_info = JSON.parse(request.body.read, symbolize_names: true)
|
29
|
-
|
30
31
|
agent_name = agent_info[:name]
|
31
|
-
capabilities = agent_info[:capabilities]
|
32
|
-
|
33
32
|
agent_uuid = SecureRandom.uuid
|
34
33
|
|
34
|
+
agent_info[:capabilities].map!{|c| c.downcase}
|
35
|
+
|
35
36
|
AGENT_REGISTRY << agent_info.merge({uuid: agent_uuid})
|
36
37
|
|
37
38
|
status 201
|
@@ -43,7 +44,7 @@ end
|
|
43
44
|
# TODO: This is a simple keyword matcher. Looking
|
44
45
|
# => for a semantic match process.
|
45
46
|
get '/discover' do
|
46
|
-
capability = params['capability']
|
47
|
+
capability = params['capability'].downcase
|
47
48
|
|
48
49
|
matching_agents = AGENT_REGISTRY.select do |agent|
|
49
50
|
agent[:capabilities].include?(capability)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
# examples/start_rabbitmq_and_registry.sh
|
3
|
+
#
|
4
|
+
# brew install rabbitmq-server
|
5
|
+
#
|
6
|
+
# Start up the AMQP message broker (RabbitM@) in the background
|
7
|
+
|
8
|
+
echo "Starting rabbitmq-server in background ..."
|
9
|
+
rabbitmq-server &
|
10
|
+
sleep 2
|
11
|
+
open http://localhost:15672/#/queues
|
12
|
+
|
13
|
+
echo
|
14
|
+
echo
|
15
|
+
echo "Starting example registry in forground ..."
|
16
|
+
echo "http://localhost:4567"
|
17
|
+
echo
|
18
|
+
ruby registry.rb # blocks until control-C
|
19
|
+
|
20
|
+
# You may have to do control-c twice to stop both
|
21
|
+
|
22
|
+
echo "#"
|
23
|
+
echo "##"
|
24
|
+
echo "###"
|
25
|
+
echo "####"
|
26
|
+
echo "#####"
|
27
|
+
echo "registry is stopped"
|
28
|
+
echo "stopping the rabbitmq server ...."
|
29
|
+
rabbitmqctl stop
|
@@ -48,9 +48,21 @@ module Agent99::AgentLifecycle
|
|
48
48
|
end
|
49
49
|
|
50
50
|
|
51
|
+
# Performs cleanup operations when the agent is shutting down.
|
52
|
+
#
|
53
|
+
def fini
|
54
|
+
if id
|
55
|
+
queue_name = id
|
56
|
+
withdraw
|
57
|
+
@message_client&.delete_queue(queue_name)
|
58
|
+
else
|
59
|
+
logger.warn('fini called with a nil id')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
51
63
|
################################################
|
52
64
|
private
|
53
|
-
|
65
|
+
|
54
66
|
# Checks if the agent is currently paused.
|
55
67
|
#
|
56
68
|
# @return [Boolean] True if the agent is paused, false otherwise
|
@@ -71,17 +83,4 @@ module Agent99::AgentLifecycle
|
|
71
83
|
end
|
72
84
|
end
|
73
85
|
end
|
74
|
-
|
75
|
-
|
76
|
-
# Performs cleanup operations when the agent is shutting down.
|
77
|
-
#
|
78
|
-
def fini
|
79
|
-
if id
|
80
|
-
queue_name = id
|
81
|
-
withdraw
|
82
|
-
@message_client&.delete_queue(queue_name)
|
83
|
-
else
|
84
|
-
logger.warn('fini called with a nil id')
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
86
|
+
end
|
@@ -6,6 +6,19 @@ require 'json_schema'
|
|
6
6
|
require 'logger'
|
7
7
|
|
8
8
|
class Agent99::AmqpMessageClient
|
9
|
+
CONFIG = {
|
10
|
+
host: "127.0.0.1",
|
11
|
+
port: 5672,
|
12
|
+
ssl: false,
|
13
|
+
vhost: "/",
|
14
|
+
user: "guest",
|
15
|
+
pass: "guest",
|
16
|
+
heartbeat: :server, # will use RabbitMQ setting
|
17
|
+
frame_max: 131072,
|
18
|
+
auth_mechanism: "PLAIN"
|
19
|
+
}
|
20
|
+
|
21
|
+
|
9
22
|
QUEUE_TTL = 60_000 # 60 seconds TTL
|
10
23
|
@instance = nil
|
11
24
|
|
@@ -17,16 +30,21 @@ class Agent99::AmqpMessageClient
|
|
17
30
|
|
18
31
|
attr_accessor :logger, :channel, :exchange
|
19
32
|
|
20
|
-
def initialize(
|
33
|
+
def initialize(
|
34
|
+
config: CONFIG,
|
35
|
+
logger: Logger.new($stdout))
|
36
|
+
@config = config
|
21
37
|
@connection = create_amqp_connection
|
22
|
-
@channel
|
23
|
-
@exchange
|
24
|
-
@logger
|
38
|
+
@channel = @connection.create_channel
|
39
|
+
@exchange = @channel.default_exchange
|
40
|
+
@logger = logger
|
25
41
|
end
|
26
42
|
|
27
43
|
def setup(agent_id:, logger:)
|
28
44
|
queue = create_queue(agent_id)
|
29
45
|
|
46
|
+
logger.info "Created queue for agent_id: #{agent_id}"
|
47
|
+
|
30
48
|
# Returning the queue to be used in the Base class
|
31
49
|
queue
|
32
50
|
end
|
@@ -66,26 +84,39 @@ class Agent99::AmqpMessageClient
|
|
66
84
|
queue_name = message.dig(:header, :to_uuid)
|
67
85
|
|
68
86
|
begin
|
87
|
+
# FIXME: message.to_json
|
69
88
|
json_payload = JSON.generate(message)
|
70
89
|
|
71
90
|
exchange.publish(json_payload, routing_key: queue_name)
|
72
91
|
|
73
|
-
logger.info "
|
92
|
+
logger.info "#{message.dig(:header,:type).to_s.upcase} message published successfully to queue: #{queue_name}"
|
74
93
|
|
75
94
|
# Return a success status
|
76
|
-
{
|
95
|
+
{
|
96
|
+
success: true,
|
97
|
+
message: "Message published successfully"
|
98
|
+
}
|
77
99
|
|
78
100
|
rescue JSON::GeneratorError => e
|
79
101
|
logger.error "Failed to convert payload to JSON: #{e.message}"
|
80
|
-
{
|
102
|
+
{
|
103
|
+
success: false,
|
104
|
+
error: "JSON conversion error: #{e.message}"
|
105
|
+
}
|
81
106
|
|
82
107
|
rescue Bunny::ConnectionClosedError, Bunny::ChannelAlreadyClosed => e
|
83
108
|
logger.error "Failed to publish message: #{e.message}"
|
84
|
-
{
|
109
|
+
{
|
110
|
+
success: false,
|
111
|
+
error: "Publishing error: #{e.message}"
|
112
|
+
}
|
85
113
|
|
86
114
|
rescue StandardError => e
|
87
115
|
logger.error "Unexpected error while publishing message: #{e.message}"
|
88
|
-
{
|
116
|
+
{
|
117
|
+
success: false,
|
118
|
+
error: "Unexpected error: #{e.message}"
|
119
|
+
}
|
89
120
|
end
|
90
121
|
end
|
91
122
|
|
@@ -109,7 +140,7 @@ class Agent99::AmqpMessageClient
|
|
109
140
|
private
|
110
141
|
|
111
142
|
def create_amqp_connection
|
112
|
-
Bunny.new.tap(&:start)
|
143
|
+
Bunny.new(@config).tap(&:start)
|
113
144
|
rescue Bunny::TCPConnectionFailed, StandardError => e
|
114
145
|
logger.error "Failed to connect to AMQP: #{e.message}"
|
115
146
|
raise "AMQP Connection Error: #{e.message}. Please check your AMQP server and try again."
|