activematrix 0.0.9 → 0.0.11

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: 763fcbfe5b98eb8dcddb9e512f782ce3c8ff994f2581e0b5a18767a52e109515
4
- data.tar.gz: cf71a8ec3f33ea722e6c3b0217e05585d2c70b07816875f602ad1bf74f054cc8
3
+ metadata.gz: bd316a48de85336049741c73b39c6876ac20b5366f4e680d4507cafe734c8300
4
+ data.tar.gz: 6a80ba9a3dc0096f2a0ce9634def8f0b74f8763819f021bbca32fb875e8b3ff1
5
5
  SHA512:
6
- metadata.gz: e6e3925d13d2708cd4b6e7e1280a3c1bfc275ad8accb353f37b7f9ea73e4886e7d69622c52bd984d307f592cdbd93482707892f5a7e64deed369296ef3b91be2
7
- data.tar.gz: c61243b17caafb3a3752b1381526ebba8995b0b6033bbc1e84584b4785cc4dcc5acb59bc5a5352ee725380721fc50fbe15ab4b1bfd69f2f2e764a61c627d9d89
6
+ metadata.gz: 305ffacaf0e9830f9c84c77d69c12ea8548831a0d03095650bb4a25a0de7322f4b2f5565b18c4cf04b65f0f43b9b7ef08a88d4a3e56ccd98da66e3bdae6130a3
7
+ data.tar.gz: 5e6005ff8af8e50e5b0865a9b0757372d6cfe3ea197db1fea13005f07d35068fb524183864ae4ac4ea8b8f43391d4a98cb1f0bcb3bcc255fad889bfc89eed9fd
@@ -43,12 +43,14 @@ module ActiveMatrix
43
43
  has_many :agent_stores, class_name: 'ActiveMatrix::AgentStore', dependent: :destroy
44
44
  has_many :chat_sessions, class_name: 'ActiveMatrix::ChatSession', dependent: :destroy
45
45
 
46
+ # Encrypt access_token for user-uploaded credentials
47
+ encrypts :access_token, deterministic: true
48
+
46
49
  # Validations
47
50
  validates :name, presence: true, uniqueness: true
48
- validates :homeserver, presence: true
49
- validates :username, presence: true
50
51
  validates :bot_class, presence: true
51
52
  validate :valid_bot_class?
53
+ validate :valid_connection_config?
52
54
 
53
55
  # Scopes
54
56
  scope :active, -> { where.not(state: %i[offline error]) }
@@ -109,12 +111,26 @@ module ActiveMatrix
109
111
  @bot_instance ||= bot_class.constantize.new(client) if running?
110
112
  end
111
113
 
114
+ # Returns a Matrix client using resolved connection config
115
+ # Resolution order:
116
+ # 1. matrix_connection → lookup from config/active_matrix.yml
117
+ # 2. Inline credentials (homeserver, access_token) → user-uploaded bots
112
118
  def client
113
- @client ||= if access_token.present?
114
- ActiveMatrix::Client.new(homeserver, access_token: access_token)
115
- else
116
- ActiveMatrix::Client.new(homeserver)
117
- end
119
+ @client ||= build_client
120
+ end
121
+
122
+ # Returns the resolved connection configuration
123
+ # @return [Hash] with :homeserver_url and :access_token keys
124
+ def connection_config
125
+ if matrix_connection.present?
126
+ ActiveMatrix.connection(matrix_connection)
127
+ else
128
+ {
129
+ homeserver_url: homeserver,
130
+ access_token: access_token,
131
+ username: username
132
+ }.compact
133
+ end
118
134
  end
119
135
 
120
136
  def running?
@@ -162,5 +178,35 @@ module ActiveMatrix
162
178
  errors.add(:bot_class, 'must be a valid class name')
163
179
  end
164
180
  end
181
+
182
+ def valid_connection_config?
183
+ if matrix_connection.present?
184
+ # Validate connection exists in YAML
185
+ errors.add(:matrix_connection, "connection '#{matrix_connection}' not found in config/active_matrix.yml") unless ActiveMatrix.connection_exists?(matrix_connection)
186
+ elsif homeserver.blank?
187
+ # Require inline credentials when no matrix_connection
188
+ errors.add(:homeserver, "can't be blank without matrix_connection")
189
+ end
190
+ end
191
+
192
+ def build_client
193
+ config = connection_config
194
+ homeserver_url = config[:homeserver_url] || config[:homeserver]
195
+
196
+ client = ActiveMatrix::Client.new(
197
+ homeserver_url,
198
+ client_cache: :some,
199
+ sync_filter_limit: config[:sync_filter_limit] || 20
200
+ )
201
+
202
+ # Authenticate
203
+ if config[:access_token].present?
204
+ client.access_token = config[:access_token]
205
+ elsif config[:username].present? && encrypted_password.present?
206
+ client.login(config[:username], BCrypt::Password.new(encrypted_password).to_s, no_sync: true)
207
+ end
208
+
209
+ client
210
+ end
165
211
  end
166
212
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # PostgreSQL 18+ required for UUIDv7 and other features
4
+ class CreateActiveMatrixTables < ActiveRecord::Migration[8.0]
5
+ def change
6
+ # Enable UUID extension
7
+ enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
8
+
9
+ # Agents table - main agent records with state machine
10
+ # Uses UUIDv7 for timestamp-sortable primary keys (PG18 feature)
11
+ create_table :active_matrix_agents, id: :uuid, default: -> { 'uuidv7()' } do |t|
12
+ t.string :name, null: false, index: { unique: true }
13
+ t.string :homeserver, null: false
14
+ t.string :username, null: false
15
+ t.string :bot_class, null: false
16
+ t.string :state, default: 'offline', null: false
17
+ t.string :access_token
18
+ t.string :encrypted_password
19
+ t.jsonb :settings, default: {} # JSONB for better indexing/querying
20
+ t.string :last_sync_token
21
+ t.datetime :last_active_at
22
+ t.integer :messages_handled, default: 0, null: false
23
+ t.timestamps
24
+ end
25
+
26
+ add_index :active_matrix_agents, :state
27
+ add_index :active_matrix_agents, :homeserver
28
+ add_index :active_matrix_agents, :settings, using: :gin # GIN index for JSONB queries
29
+
30
+ # Agent Store table - per-agent key-value storage
31
+ create_table :active_matrix_agent_stores, id: :uuid, default: -> { 'uuidv7()' } do |t|
32
+ t.references :agent, null: false, foreign_key: { to_table: :active_matrix_agents }, type: :uuid
33
+ t.string :key, null: false
34
+ t.jsonb :value, default: {}
35
+ t.datetime :expires_at
36
+ t.timestamps
37
+ end
38
+
39
+ add_index :active_matrix_agent_stores, %i[agent_id key], unique: true
40
+ add_index :active_matrix_agent_stores, :expires_at, where: 'expires_at IS NOT NULL'
41
+
42
+ # Chat Session table - per-user/room conversation state
43
+ create_table :active_matrix_chat_sessions, id: :uuid, default: -> { 'uuidv7()' } do |t|
44
+ t.references :agent, null: false, foreign_key: { to_table: :active_matrix_agents }, type: :uuid
45
+ t.string :user_id, null: false
46
+ t.string :room_id, null: false
47
+ t.jsonb :context, default: {}
48
+ t.jsonb :message_history, default: { 'messages' => [] }
49
+ t.datetime :last_message_at
50
+ t.integer :message_count, default: 0, null: false
51
+ t.timestamps
52
+ end
53
+
54
+ add_index :active_matrix_chat_sessions, %i[agent_id user_id room_id],
55
+ unique: true, name: 'index_chat_sessions_on_agent_user_room'
56
+ add_index :active_matrix_chat_sessions, :last_message_at
57
+ add_index :active_matrix_chat_sessions, :room_id
58
+
59
+ # Knowledge Base table - shared storage across all agents
60
+ create_table :active_matrix_knowledge_bases, id: :uuid, default: -> { 'uuidv7()' } do |t|
61
+ t.string :key, null: false, index: { unique: true }
62
+ t.jsonb :value, default: {}
63
+ t.string :category
64
+ t.datetime :expires_at
65
+ t.boolean :public_read, default: true, null: false
66
+ t.boolean :public_write, default: false, null: false
67
+ t.timestamps
68
+ end
69
+
70
+ add_index :active_matrix_knowledge_bases, :category
71
+ add_index :active_matrix_knowledge_bases, :expires_at, where: 'expires_at IS NOT NULL'
72
+ end
73
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddMatrixConnectionToAgents < ActiveRecord::Migration[8.0]
4
+ def change
5
+ # Add matrix_connection to reference YAML-configured connections
6
+ # When set, credentials come from config/active_matrix.yml
7
+ # When nil, uses inline homeserver/access_token columns (for user-uploaded bots)
8
+ add_column :active_matrix_agents, :matrix_connection, :string
9
+
10
+ # Make credential columns nullable - they're optional when using matrix_connection
11
+ change_column_null :active_matrix_agents, :homeserver, true
12
+ change_column_null :active_matrix_agents, :username, true
13
+
14
+ add_index :active_matrix_agents, :matrix_connection
15
+ end
16
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'erb'
5
+ require 'singleton'
6
+
7
+ module ActiveMatrix
8
+ # Registry for named Matrix connections loaded from config/active_matrix.yml
9
+ #
10
+ # Connections are defined in YAML with ERB support for secrets:
11
+ #
12
+ # primary:
13
+ # homeserver_url: <%= ENV['MATRIX_HOMESERVER_URL'] %>
14
+ # access_token: <%= ENV['MATRIX_ACCESS_TOKEN'] %>
15
+ #
16
+ # Usage:
17
+ # ActiveMatrix.connection(:primary) # Returns connection config hash
18
+ # ActiveMatrix.client(:primary) # Returns authenticated Client
19
+ #
20
+ class ConnectionRegistry
21
+ include Singleton
22
+
23
+ class ConnectionNotFound < StandardError; end
24
+
25
+ def initialize
26
+ @connections = {}
27
+ @clients = {}
28
+ @mutex = Mutex.new
29
+ end
30
+
31
+ # Load connections from YAML file
32
+ # @param path [String, Pathname] path to YAML config file
33
+ def load!(path)
34
+ @mutex.synchronize do
35
+ @connections = load_yaml(path)
36
+ @clients.clear # Clear cached clients when config reloads
37
+ end
38
+ end
39
+
40
+ # Get connection configuration by name
41
+ # @param name [Symbol, String] connection name (default: :primary)
42
+ # @return [Hash] connection configuration
43
+ # @raise [ConnectionNotFound] if connection doesn't exist
44
+ def connection(name = :primary)
45
+ name = name.to_s
46
+ config = @connections[name]
47
+ raise ConnectionNotFound, "Connection '#{name}' not found in config/active_matrix.yml" unless config
48
+
49
+ config.symbolize_keys
50
+ end
51
+
52
+ # Get or create a client for a named connection
53
+ # @param name [Symbol, String] connection name (default: :primary)
54
+ # @return [ActiveMatrix::Client] authenticated client
55
+ def client(name = :primary)
56
+ name = name.to_s
57
+
58
+ @mutex.synchronize do
59
+ @clients[name] ||= build_client(name)
60
+ end
61
+ end
62
+
63
+ # Check if a connection exists
64
+ # @param name [Symbol, String] connection name
65
+ # @return [Boolean]
66
+ def connection_exists?(name)
67
+ @connections.key?(name.to_s)
68
+ end
69
+
70
+ # List all available connection names
71
+ # @return [Array<String>]
72
+ def connection_names
73
+ @connections.keys - ['default']
74
+ end
75
+
76
+ # Clear all cached clients (useful for testing)
77
+ def clear_clients!
78
+ @mutex.synchronize do
79
+ @clients.each_value do |client|
80
+ client.logout if client.logged_in?
81
+ rescue StandardError
82
+ # Ignore cleanup errors
83
+ end
84
+ @clients.clear
85
+ end
86
+ end
87
+
88
+ # Reload configuration from file
89
+ # @param path [String, Pathname] path to YAML config file
90
+ def reload!(path = nil)
91
+ path ||= default_config_path
92
+ clear_clients!
93
+ load!(path)
94
+ end
95
+
96
+ private
97
+
98
+ def load_yaml(path)
99
+ return {} unless File.exist?(path)
100
+
101
+ yaml_content = File.read(path)
102
+ erb_result = ERB.new(yaml_content).result
103
+ config = YAML.safe_load(erb_result, permitted_classes: [], permitted_symbols: [], aliases: true) || {}
104
+
105
+ # Filter out 'default' key which is just for YAML anchors
106
+ config.except('default')
107
+ end
108
+
109
+ def build_client(name)
110
+ config = connection(name)
111
+
112
+ raise ConnectionNotFound, "Connection '#{name}' missing homeserver_url" unless config[:homeserver_url]
113
+
114
+ client = ActiveMatrix::Client.new(
115
+ config[:homeserver_url],
116
+ client_cache: :some,
117
+ sync_filter_limit: config[:sync_filter_limit] || 20
118
+ )
119
+
120
+ # Authenticate if access_token provided
121
+ if config[:access_token]
122
+ client.access_token = config[:access_token]
123
+ elsif config[:username] && config[:password]
124
+ client.login(config[:username], config[:password], no_sync: true)
125
+ end
126
+
127
+ client
128
+ end
129
+
130
+ def default_config_path
131
+ return Rails.root.join('config/active_matrix.yml') if defined?(Rails)
132
+
133
+ File.join(Dir.pwd, 'config', 'active_matrix.yml')
134
+ end
135
+ end
136
+ end
@@ -4,17 +4,22 @@ require 'rails/railtie'
4
4
 
5
5
  module ActiveMatrix
6
6
  class Railtie < Rails::Railtie
7
- Rails.logger.debug 'ActiveMatrix::Railtie: Loading...'
8
-
9
- initializer 'activematrix.configure_rails_initialization' do
7
+ initializer 'active_matrix.configure_rails_initialization' do
10
8
  Rails.logger.debug 'ActiveMatrix::Railtie: Initializer running'
11
- # Configure Rails.logger as the default logger
12
9
  ActiveMatrix.logger = Rails.logger
13
- Rails.logger.debug 'ActiveMatrix::Railtie: Logger configured'
10
+ end
11
+
12
+ # Load connection config after application initializers run
13
+ # This ensures user's initializer can set config.default_connection
14
+ config.after_initialize do
15
+ config_path = Rails.root.join('config/active_matrix.yml')
14
16
 
15
- # Debug autoload paths
16
- Rails.logger.debug { "ActiveMatrix::Railtie: Autoload paths = #{Rails.application.config.autoload_paths}" }
17
- Rails.logger.debug { "ActiveMatrix::Railtie: Eager load paths = #{Rails.application.config.eager_load_paths}" }
17
+ if File.exist?(config_path)
18
+ Rails.logger.debug 'ActiveMatrix::Railtie: Loading connection config'
19
+ ActiveMatrix::ConnectionRegistry.instance.load!(config_path)
20
+ else
21
+ Rails.logger.debug 'ActiveMatrix::Railtie: No config/active_matrix.yml found'
22
+ end
18
23
  end
19
24
  end
20
25
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveMatrix
4
- VERSION = '0.0.9'
4
+ VERSION = '0.0.11'
5
5
  end
data/lib/active_matrix.rb CHANGED
@@ -78,6 +78,36 @@ module ActiveMatrix
78
78
  def global_logger?
79
79
  instance_variable_defined?(:@logger)
80
80
  end
81
+
82
+ # Get a client for a named connection
83
+ # @param name [Symbol, String] connection name (default: :primary)
84
+ # @return [ActiveMatrix::Client] authenticated client
85
+ # @example
86
+ # ActiveMatrix.client.send_message(room_id, message)
87
+ # ActiveMatrix.client(:notifications).send_notice(room_id, notice)
88
+ def client(name = :primary)
89
+ ConnectionRegistry.instance.client(name)
90
+ end
91
+
92
+ # Get connection configuration by name
93
+ # @param name [Symbol, String] connection name (default: :primary)
94
+ # @return [Hash] connection configuration
95
+ def connection(name = :primary)
96
+ ConnectionRegistry.instance.connection(name)
97
+ end
98
+
99
+ # Check if a connection exists
100
+ # @param name [Symbol, String] connection name
101
+ # @return [Boolean]
102
+ def connection_exists?(name)
103
+ ConnectionRegistry.instance.connection_exists?(name)
104
+ end
105
+
106
+ # List all available connection names
107
+ # @return [Array<String>]
108
+ def connection_names
109
+ ConnectionRegistry.instance.connection_names
110
+ end
81
111
  end
82
112
 
83
113
  # Set up Zeitwerk loader
@@ -11,28 +11,38 @@ module ActiveMatrix
11
11
 
12
12
  argument :commands, type: :array, default: [], banner: 'command1 command2'
13
13
 
14
+ check_class_collision suffix: 'Bot'
15
+
14
16
  def create_bot_file
15
17
  template 'bot.rb.erb', "app/bots/#{file_name}_bot.rb"
16
18
  end
17
19
 
18
- def create_bot_spec
19
- template 'bot_spec.rb.erb', "spec/bots/#{file_name}_bot_spec.rb"
20
+ def create_bot_test
21
+ template 'bot_test.rb.erb', "test/bots/#{file_name}_bot_test.rb"
20
22
  end
21
23
 
22
24
  def display_usage
23
25
  say "\nBot created! To use your bot:\n\n"
24
26
  say '1. Create an agent in Rails console:'
25
- say ' agent = MatrixAgent.create!('
26
- say " name: '#{file_name}',"
27
- say " homeserver: 'https://matrix.org',"
28
- say " username: 'your_bot_username',"
29
- say " password: 'your_bot_password',"
27
+ say ' agent = ActiveMatrix::Agent.create!('
28
+ say " name: '#{file_name.dasherize}',"
29
+ say " matrix_connection: 'primary',"
30
30
  say " bot_class: '#{class_name}Bot'"
31
31
  say ' )'
32
- say "\n2. Start the agent:"
33
- say ' ActiveMatrix::AgentManager.instance.start_agent(agent)'
32
+ say "\n2. Start the daemon:"
33
+ say ' bundle exec activematrix start'
34
34
  say "\n"
35
35
  end
36
+
37
+ private
38
+
39
+ def file_name
40
+ @_file_name ||= remove_possible_suffix(super)
41
+ end
42
+
43
+ def remove_possible_suffix(name)
44
+ name.sub(/_?bot$/i, '')
45
+ end
36
46
  end
37
47
  end
38
48
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class <%= class_name %>BotTest < ActiveSupport::TestCase
6
+ setup do
7
+ @agent = ActiveMatrix::Agent.create!(
8
+ name: '<%= file_name %>_test',
9
+ homeserver: 'https://matrix.example.com',
10
+ username: 'test_bot',
11
+ bot_class: '<%= class_name %>Bot'
12
+ )
13
+ end
14
+
15
+ test 'bot class is valid' do
16
+ assert_equal '<%= class_name %>Bot', @agent.bot_class
17
+ assert @agent.valid?
18
+ end
19
+ <% if commands.any? %>
20
+ <% commands.each do |command| %>
21
+ test '<%= command %> command is defined' do
22
+ bot_class = @agent.bot_class.constantize
23
+ assert bot_class.commands.key?(:<%= command %>)
24
+ end
25
+ <% end %>
26
+ <% else %>
27
+ test 'hello command is defined' do
28
+ bot_class = @agent.bot_class.constantize
29
+ assert bot_class.commands.key?(:hello)
30
+ end
31
+
32
+ test 'broadcast command is defined' do
33
+ bot_class = @agent.bot_class.constantize
34
+ assert bot_class.commands.key?(:broadcast)
35
+ end
36
+ <% end %>
37
+ end
@@ -7,12 +7,16 @@ module ActiveMatrix
7
7
  class InstallGenerator < Rails::Generators::Base
8
8
  source_root File.expand_path('templates', __dir__)
9
9
 
10
- desc 'Installs ActiveMatrix initializer and copies migrations'
10
+ desc 'Installs ActiveMatrix configuration and copies migrations'
11
11
 
12
12
  def copy_migrations
13
13
  rails_command 'active_matrix:install:migrations', inline: true
14
14
  end
15
15
 
16
+ def create_connection_config
17
+ template 'active_matrix.yml', 'config/active_matrix.yml'
18
+ end
19
+
16
20
  def create_initializer
17
21
  template 'active_matrix.rb', 'config/initializers/active_matrix.rb'
18
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activematrix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -420,6 +420,8 @@ files:
420
420
  - app/models/active_matrix/application_record.rb
421
421
  - app/models/active_matrix/chat_session.rb
422
422
  - app/models/active_matrix/knowledge_base.rb
423
+ - db/migrate/20251201000000_create_active_matrix_tables.rb
424
+ - db/migrate/20251229000000_add_matrix_connection_to_agents.rb
423
425
  - exe/activematrix
424
426
  - lib/active_matrix.rb
425
427
  - lib/active_matrix/account_data_cache.rb
@@ -436,6 +438,7 @@ files:
436
438
  - lib/active_matrix/cli.rb
437
439
  - lib/active_matrix/client.rb
438
440
  - lib/active_matrix/client_pool.rb
441
+ - lib/active_matrix/connection_registry.rb
439
442
  - lib/active_matrix/daemon.rb
440
443
  - lib/active_matrix/daemon/probe_server.rb
441
444
  - lib/active_matrix/daemon/signal_handler.rb
@@ -473,7 +476,7 @@ files:
473
476
  - lib/activematrix.rb
474
477
  - lib/generators/active_matrix/bot/bot_generator.rb
475
478
  - lib/generators/active_matrix/bot/templates/bot.rb.erb
476
- - lib/generators/active_matrix/bot/templates/bot_spec.rb.erb
479
+ - lib/generators/active_matrix/bot/templates/bot_test.rb.erb
477
480
  - lib/generators/active_matrix/install/install_generator.rb
478
481
  - lib/generators/active_matrix/install/templates/README
479
482
  - lib/generators/active_matrix/install/templates/active_matrix.rb
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_helper'
4
-
5
- RSpec.describe <%= class_name %>Bot do
6
- let(:agent) do
7
- MatrixAgent.create!(
8
- name: '<%= file_name %>_test',
9
- homeserver: 'https://matrix.example.com',
10
- username: 'test_bot',
11
- password: 'password',
12
- bot_class: '<%= class_name %>Bot'
13
- )
14
- end
15
-
16
- let(:client) { instance_double(ActiveMatrix::Client) }
17
- let(:room) { instance_double(ActiveMatrix::Room) }
18
- let(:bot) { described_class.new(agent) }
19
-
20
- before do
21
- allow(agent).to receive(:client).and_return(client)
22
- allow(client).to receive(:mxid).and_return('@test_bot:example.com')
23
- allow(bot).to receive(:room).and_return(room)
24
- end
25
-
26
- <% if commands.any? %>
27
- <% commands.each do |command| %>
28
- describe '#<%= command %>' do
29
- it 'responds to the <%= command %> command' do
30
- expect(room).to receive(:send_notice).twice
31
- bot.send(:<%= command %>)
32
- end
33
- end
34
-
35
- <% end %>
36
- <% else %>
37
- describe '#hello' do
38
- it 'responds with a greeting' do
39
- expect(room).to receive(:send_notice).with('Hello there!')
40
- bot.hello
41
- end
42
-
43
- it 'greets by name when provided' do
44
- expect(room).to receive(:send_notice).with('Hello, Alice!')
45
- bot.hello('Alice')
46
- end
47
- end
48
- <% end %>
49
-
50
- describe 'inter-agent communication' do
51
- let(:other_bot) { instance_double(ActiveMatrix::Bot::MultiInstanceBase, agent_name: 'other_bot') }
52
-
53
- it 'responds to ping messages' do
54
- expect(bot).to receive(:send_to_agent).with('other_bot', hash_including(type: 'pong'))
55
- bot.receive_message({ type: 'ping' }, from: other_bot)
56
- end
57
- end
58
-
59
- describe 'memory access' do
60
- it 'has access to agent memory' do
61
- expect(bot.memory).to be_a(ActiveMatrix::Memory::AgentMemory)
62
- end
63
-
64
- it 'has access to global memory' do
65
- expect(bot.global_memory).to be_a(ActiveMatrix::Memory::GlobalMemory)
66
- end
67
- end
68
- end