activematrix 0.0.7 → 0.0.9

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -28
  3. data/app/models/active_matrix/agent.rb +36 -1
  4. data/app/models/active_matrix/agent_store.rb +29 -0
  5. data/app/models/active_matrix/application_record.rb +8 -0
  6. data/app/models/active_matrix/chat_session.rb +29 -0
  7. data/app/models/active_matrix/knowledge_base.rb +26 -0
  8. data/exe/activematrix +7 -0
  9. data/lib/active_matrix/agent_manager.rb +160 -121
  10. data/lib/active_matrix/agent_registry.rb +25 -21
  11. data/lib/active_matrix/api.rb +8 -2
  12. data/lib/active_matrix/async_query.rb +58 -0
  13. data/lib/active_matrix/bot/base.rb +3 -3
  14. data/lib/active_matrix/bot/builtin_commands.rb +188 -0
  15. data/lib/active_matrix/bot/command_parser.rb +175 -0
  16. data/lib/active_matrix/cli.rb +273 -0
  17. data/lib/active_matrix/client.rb +21 -6
  18. data/lib/active_matrix/client_pool.rb +38 -27
  19. data/lib/active_matrix/daemon/probe_server.rb +118 -0
  20. data/lib/active_matrix/daemon/signal_handler.rb +156 -0
  21. data/lib/active_matrix/daemon/worker.rb +109 -0
  22. data/lib/active_matrix/daemon.rb +236 -0
  23. data/lib/active_matrix/engine.rb +7 -3
  24. data/lib/active_matrix/errors.rb +1 -1
  25. data/lib/active_matrix/event_router.rb +61 -49
  26. data/lib/active_matrix/events.rb +1 -0
  27. data/lib/active_matrix/instrumentation.rb +148 -0
  28. data/lib/active_matrix/memory/agent_memory.rb +7 -21
  29. data/lib/active_matrix/memory/conversation_memory.rb +4 -20
  30. data/lib/active_matrix/memory/global_memory.rb +15 -30
  31. data/lib/active_matrix/message_dispatcher.rb +197 -0
  32. data/lib/active_matrix/metrics.rb +424 -0
  33. data/lib/active_matrix/presence_manager.rb +181 -0
  34. data/lib/active_matrix/telemetry.rb +134 -0
  35. data/lib/active_matrix/version.rb +1 -1
  36. data/lib/active_matrix.rb +12 -2
  37. data/lib/generators/active_matrix/install/install_generator.rb +3 -15
  38. data/lib/generators/active_matrix/install/templates/README +5 -2
  39. data/lib/generators/active_matrix/install/templates/active_matrix.yml +32 -0
  40. metadata +142 -45
  41. data/lib/active_matrix/protocols/cs/message_relationships.rb +0 -318
  42. data/lib/generators/active_matrix/install/templates/create_agent_memories.rb +0 -17
  43. data/lib/generators/active_matrix/install/templates/create_conversation_contexts.rb +0 -21
  44. data/lib/generators/active_matrix/install/templates/create_global_memories.rb +0 -20
  45. data/lib/generators/active_matrix/install/templates/create_matrix_agents.rb +0 -26
@@ -1,318 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveMatrix
4
- module Protocols
5
- module CS
6
- # Handles message relationships (replies, edits, reactions, threads)
7
- # @see https://spec.matrix.org/latest/client-server-api/#relationships
8
- module MessageRelationships
9
- # Send a message as a reply to another event
10
- #
11
- # @param room_id [String] The room ID
12
- # @param event_id [String] The event ID to reply to
13
- # @param content [Hash,String] The message content
14
- # @param msgtype [String] The message type (defaults to 'm.text')
15
- # @param params [Hash] Additional parameters for send_message_event
16
- # @return [Response] The response from the server
17
- #
18
- # @example Reply to a message
19
- # api.reply_to(room_id, event_id, "I agree!")
20
- #
21
- # @example Reply with custom msgtype
22
- # api.reply_to(room_id, event_id, { url: "mxc://..." }, msgtype: 'm.image')
23
- def reply_to(room_id, event_id, content, msgtype: 'm.text', **params)
24
- content = { body: content } if content.is_a?(String)
25
- content[:msgtype] ||= msgtype
26
-
27
- # Add the reply relationship
28
- content[:'m.relates_to'] = {
29
- 'm.in_reply_to' => {
30
- event_id: event_id
31
- }
32
- }
33
-
34
- send_message_event(room_id, 'm.room.message', content, **params)
35
- end
36
-
37
- # Send a threaded message
38
- #
39
- # @param room_id [String] The room ID
40
- # @param thread_root_id [String] The root event ID of the thread
41
- # @param content [Hash,String] The message content
42
- # @param msgtype [String] The message type (defaults to 'm.text')
43
- # @param latest_event_id [String,nil] The latest event in the thread (for fallback)
44
- # @param include_fallback [Boolean] Include reply fallback for older clients
45
- # @param params [Hash] Additional parameters for send_message_event
46
- # @return [Response] The response from the server
47
- #
48
- # @example Send a threaded message
49
- # api.send_threaded_message(room_id, root_event_id, "This is a thread reply")
50
- #
51
- # @example Send a threaded message with fallback
52
- # api.send_threaded_message(room_id, root_id, "Reply", latest_event_id: last_id, include_fallback: true)
53
- def send_threaded_message(room_id, thread_root_id, content, msgtype: 'm.text',
54
- latest_event_id: nil, include_fallback: false, **params)
55
- content = { body: content } if content.is_a?(String)
56
- content[:msgtype] ||= msgtype
57
-
58
- # Build the thread relationship
59
- relates_to = {
60
- rel_type: 'm.thread',
61
- event_id: thread_root_id
62
- }
63
-
64
- # Add fallback for older clients if requested
65
- if include_fallback && latest_event_id
66
- relates_to[:'m.in_reply_to'] = {
67
- event_id: latest_event_id
68
- }
69
- relates_to[:is_falling_back] = true
70
- end
71
-
72
- content[:'m.relates_to'] = relates_to
73
- send_message_event(room_id, 'm.room.message', content, **params)
74
- end
75
-
76
- # Edit an existing message
77
- #
78
- # @param room_id [String] The room ID
79
- # @param event_id [String] The event ID to edit
80
- # @param new_content [Hash,String] The new message content
81
- # @param msgtype [String] The message type (defaults to 'm.text')
82
- # @param params [Hash] Additional parameters for send_message_event
83
- # @return [Response] The response from the server
84
- #
85
- # @example Edit a text message
86
- # api.edit_message(room_id, event_id, "Updated message content")
87
- #
88
- # @example Edit with formatted content
89
- # api.edit_message(room_id, event_id, {
90
- # body: "Updated *formatted* message",
91
- # format: "org.matrix.custom.html",
92
- # formatted_body: "Updated <em>formatted</em> message"
93
- # })
94
- def edit_message(room_id, event_id, new_content, msgtype: 'm.text', **params)
95
- new_content = { body: new_content } if new_content.is_a?(String)
96
- new_content[:msgtype] ||= msgtype
97
-
98
- # Build the edit event content
99
- content = {
100
- body: "* #{new_content[:body]}", # Fallback with asterisk prefix
101
- msgtype: msgtype,
102
- 'm.new_content' => new_content,
103
- 'm.relates_to' => {
104
- rel_type: 'm.replace',
105
- event_id: event_id
106
- }
107
- }
108
-
109
- # Copy format fields to top level if present
110
- if new_content[:format]
111
- content[:format] = new_content[:format]
112
- content[:formatted_body] = "* #{new_content[:formatted_body]}"
113
- end
114
-
115
- send_message_event(room_id, 'm.room.message', content, **params)
116
- end
117
-
118
- # Send a reaction to an event
119
- #
120
- # @param room_id [String] The room ID
121
- # @param event_id [String] The event ID to react to
122
- # @param key [String] The reaction key (usually an emoji)
123
- # @param params [Hash] Additional parameters for send_message_event
124
- # @return [Response] The response from the server
125
- #
126
- # @example React with an emoji
127
- # api.send_reaction(room_id, event_id, "👍")
128
- #
129
- # @example React with custom text
130
- # api.send_reaction(room_id, event_id, "agree")
131
- def send_reaction(room_id, event_id, key, **params)
132
- content = {
133
- 'm.relates_to' => {
134
- rel_type: 'm.annotation',
135
- event_id: event_id,
136
- key: key
137
- }
138
- }
139
-
140
- send_message_event(room_id, 'm.reaction', content, **params)
141
- end
142
-
143
- # Remove a reaction from an event
144
- #
145
- # @param room_id [String] The room ID
146
- # @param reaction_event_id [String] The reaction event ID to redact
147
- # @param reason [String,nil] Optional reason for removing the reaction
148
- # @param params [Hash] Additional parameters for redact_event
149
- # @return [Response] The response from the server
150
- #
151
- # @example Remove a reaction
152
- # api.remove_reaction(room_id, reaction_event_id)
153
- def remove_reaction(room_id, reaction_event_id, reason: nil, **params)
154
- redact_event(room_id, reaction_event_id, reason: reason, **params)
155
- end
156
-
157
- # Send a reference to another event
158
- #
159
- # @param room_id [String] The room ID
160
- # @param event_id [String] The event ID to reference
161
- # @param content [Hash,String] The message content
162
- # @param msgtype [String] The message type (defaults to 'm.text')
163
- # @param params [Hash] Additional parameters for send_message_event
164
- # @return [Response] The response from the server
165
- #
166
- # @example Send a message referencing another event
167
- # api.send_reference(room_id, event_id, "See the above message")
168
- def send_reference(room_id, event_id, content, msgtype: 'm.text', **params)
169
- content = { body: content } if content.is_a?(String)
170
- content[:msgtype] ||= msgtype
171
-
172
- content[:'m.relates_to'] = {
173
- rel_type: 'm.reference',
174
- event_id: event_id
175
- }
176
-
177
- send_message_event(room_id, 'm.room.message', content, **params)
178
- end
179
-
180
- # Get related events for a given event
181
- #
182
- # @param room_id [String] The room ID
183
- # @param event_id [String] The event ID to get relations for
184
- # @param rel_type [String,nil] Filter by specific relationship type
185
- # @param event_type [String,nil] Filter by specific event type
186
- # @param from [String,nil] Pagination token
187
- # @param to [String,nil] Pagination token
188
- # @param limit [Integer] Maximum number of events to return
189
- # @param direction [String] Direction of pagination ('b' for backwards, 'f' for forwards)
190
- # @param params [Hash] Additional query parameters
191
- # @return [Response] The related events
192
- #
193
- # @example Get all relations
194
- # api.get_relations(room_id, event_id)
195
- #
196
- # @example Get only reactions
197
- # api.get_relations(room_id, event_id, rel_type: 'm.annotation', event_type: 'm.reaction')
198
- #
199
- # @example Get thread replies
200
- # api.get_relations(room_id, event_id, rel_type: 'm.thread')
201
- def get_relations(room_id, event_id, rel_type: nil, event_type: nil,
202
- from: nil, to: nil, limit: nil, direction: 'b', **params)
203
- query = {
204
- from: from,
205
- to: to,
206
- limit: limit,
207
- dir: direction
208
- }.merge(params).compact
209
-
210
- # Build the appropriate endpoint based on filters
211
- endpoint = "/rooms/#{room_id}/relations/#{event_id}"
212
- endpoint += "/#{rel_type}" if rel_type
213
- endpoint += "/#{event_type}" if rel_type && event_type
214
-
215
- request(:get, client_api_latest, endpoint, query: query)
216
- end
217
-
218
- # Get aggregated relations for multiple events
219
- #
220
- # @param room_id [String] The room ID
221
- # @param event_ids [Array<String>] The event IDs to get relations for
222
- # @param rel_type [String,nil] Filter by specific relationship type
223
- # @param event_type [String,nil] Filter by specific event type
224
- # @param params [Hash] Additional body parameters
225
- # @return [Response] The aggregated relations
226
- #
227
- # @example Get aggregated reactions for multiple events
228
- # api.get_aggregated_relations(room_id, event_ids, rel_type: 'm.annotation')
229
- def get_aggregated_relations(room_id, event_ids, rel_type: nil, event_type: nil, **params)
230
- body = {
231
- event_ids: event_ids,
232
- rel_type: rel_type,
233
- event_type: event_type
234
- }.merge(params).compact
235
-
236
- request(:post, client_api_latest, "/rooms/#{room_id}/aggregations", body: body)
237
- end
238
-
239
- # Get the edit history of an event
240
- #
241
- # @param room_id [String] The room ID
242
- # @param event_id [String] The event ID to get edit history for
243
- # @param params [Hash] Additional query parameters
244
- # @return [Response] The edit history
245
- #
246
- # @example Get edit history
247
- # api.get_edit_history(room_id, event_id)
248
- def get_edit_history(room_id, event_id, **params)
249
- get_relations(room_id, event_id, rel_type: 'm.replace', event_type: 'm.room.message', **params)
250
- end
251
-
252
- # Get all reactions for an event
253
- #
254
- # @param room_id [String] The room ID
255
- # @param event_id [String] The event ID to get reactions for
256
- # @param params [Hash] Additional query parameters
257
- # @return [Response] The reactions
258
- #
259
- # @example Get all reactions
260
- # api.get_reactions(room_id, event_id)
261
- def get_reactions(room_id, event_id, **params)
262
- get_relations(room_id, event_id, rel_type: 'm.annotation', event_type: 'm.reaction', **params)
263
- end
264
-
265
- # Get thread messages for a root event
266
- #
267
- # @param room_id [String] The room ID
268
- # @param thread_root_id [String] The thread root event ID
269
- # @param params [Hash] Additional query parameters
270
- # @return [Response] The thread messages
271
- #
272
- # @example Get thread messages
273
- # api.get_thread_messages(room_id, thread_root_id)
274
- def get_thread_messages(room_id, thread_root_id, **params)
275
- get_relations(room_id, thread_root_id, rel_type: 'm.thread', **params)
276
- end
277
-
278
- # Check if an event has been edited
279
- #
280
- # @param event [Hash] The event to check
281
- # @return [Boolean] True if the event has been edited
282
- def event_edited?(event)
283
- event.dig(:unsigned, :'m.relations', :'m.replace').present?
284
- end
285
-
286
- # Get the latest edit content for an event
287
- #
288
- # @param event [Hash] The event to get latest content for
289
- # @return [Hash,nil] The latest content or nil if not edited
290
- def get_latest_edit_content(event)
291
- edit_event = event.dig(:unsigned, :'m.relations', :'m.replace')
292
- return nil unless edit_event
293
-
294
- # Return the m.new_content if available, otherwise the content
295
- edit_event.dig(:content, :'m.new_content') || edit_event[:content]
296
- end
297
-
298
- # Check if an event is part of a thread
299
- #
300
- # @param event [Hash] The event to check
301
- # @return [Boolean] True if the event is part of a thread
302
- def in_thread?(event)
303
- rel_type = event.dig(:content, :'m.relates_to', :rel_type)
304
- rel_type == 'm.thread'
305
- end
306
-
307
- # Get the thread root ID for an event
308
- #
309
- # @param event [Hash] The event to get thread root for
310
- # @return [String,nil] The thread root event ID or nil if not in a thread
311
- def get_thread_root_id(event)
312
- return nil unless in_thread?(event)
313
- event.dig(:content, :'m.relates_to', :event_id)
314
- end
315
- end
316
- end
317
- end
318
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateAgentMemories < ActiveRecord::Migration<%= migration_version %>
4
- def change
5
- create_table :agent_memories do |t|
6
- t.references :matrix_agent, null: false, foreign_key: true
7
- t.string :key, null: false
8
- t.jsonb :value, default: {}
9
- t.datetime :expires_at
10
-
11
- t.timestamps
12
-
13
- t.index [:matrix_agent_id, :key], unique: true
14
- t.index :expires_at
15
- end
16
- end
17
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateConversationContexts < ActiveRecord::Migration<%= migration_version %>
4
- def change
5
- create_table :conversation_contexts do |t|
6
- t.references :matrix_agent, null: false, foreign_key: true
7
- t.string :user_id, null: false
8
- t.string :room_id, null: false
9
- t.jsonb :context, default: {}
10
- t.jsonb :message_history, default: { messages: [] }
11
- t.datetime :last_message_at
12
- t.integer :message_count, default: 0
13
-
14
- t.timestamps
15
-
16
- t.index [:matrix_agent_id, :user_id, :room_id], unique: true, name: 'idx_conv_context_unique'
17
- t.index :last_message_at
18
- t.index :room_id
19
- end
20
- end
21
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateGlobalMemories < ActiveRecord::Migration<%= migration_version %>
4
- def change
5
- create_table :global_memories do |t|
6
- t.string :key, null: false
7
- t.jsonb :value, default: {}
8
- t.string :category
9
- t.datetime :expires_at
10
- t.boolean :public_read, default: true
11
- t.boolean :public_write, default: false
12
-
13
- t.timestamps
14
-
15
- t.index :key, unique: true
16
- t.index :category
17
- t.index :expires_at
18
- end
19
- end
20
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateMatrixAgents < ActiveRecord::Migration<%= migration_version %>
4
- def change
5
- create_table :matrix_agents do |t|
6
- t.string :name, null: false
7
- t.string :homeserver, null: false
8
- t.string :username, null: false
9
- t.string :encrypted_password
10
- t.string :access_token
11
- t.string :state, default: 'offline', null: false
12
- t.string :bot_class, null: false
13
- t.jsonb :settings, default: {}
14
- t.string :last_sync_token
15
- t.datetime :last_active_at
16
- t.integer :rooms_count, default: 0
17
- t.integer :messages_handled, default: 0
18
-
19
- t.timestamps
20
-
21
- t.index :name, unique: true
22
- t.index :state
23
- t.index [:homeserver, :username]
24
- end
25
- end
26
- end