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 +4 -4
- data/app/models/active_matrix/agent.rb +53 -7
- data/db/migrate/20251201000000_create_active_matrix_tables.rb +73 -0
- data/db/migrate/20251229000000_add_matrix_connection_to_agents.rb +16 -0
- data/lib/active_matrix/connection_registry.rb +136 -0
- data/lib/active_matrix/railtie.rb +13 -8
- data/lib/active_matrix/version.rb +1 -1
- data/lib/active_matrix.rb +30 -0
- data/lib/generators/active_matrix/bot/bot_generator.rb +19 -9
- data/lib/generators/active_matrix/bot/templates/bot_test.rb.erb +37 -0
- data/lib/generators/active_matrix/install/install_generator.rb +5 -1
- metadata +5 -2
- data/lib/generators/active_matrix/bot/templates/bot_spec.rb.erb +0 -68
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd316a48de85336049741c73b39c6876ac20b5366f4e680d4507cafe734c8300
|
|
4
|
+
data.tar.gz: 6a80ba9a3dc0096f2a0ce9634def8f0b74f8763819f021bbca32fb875e8b3ff1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 ||=
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
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
|
|
19
|
-
template '
|
|
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 =
|
|
26
|
-
say " name: '#{file_name}',"
|
|
27
|
-
say "
|
|
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
|
|
33
|
-
say '
|
|
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
|
|
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.
|
|
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/
|
|
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
|