@elizaos/plugin-matrix 2.0.0-alpha.7 → 2.0.0-beta.1

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.
package/dist/index.js CHANGED
@@ -1,8 +1,119 @@
1
1
  // src/index.ts
2
- import { logger as logger2 } from "@elizaos/core";
2
+ import { getConnectorAccountManager, logger as logger2 } from "@elizaos/core";
3
3
 
4
+ // src/accounts.ts
5
+ var DEFAULT_MATRIX_ACCOUNT_ID = "default";
6
+ function stringSetting(runtime, key) {
7
+ const value = runtime.getSetting(key);
8
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
9
+ }
10
+ function characterConfig(runtime) {
11
+ const settings = runtime.character?.settings;
12
+ const raw = settings?.matrix;
13
+ return raw && typeof raw === "object" ? raw : {};
14
+ }
15
+ function parseAccountsJson(runtime) {
16
+ const raw = stringSetting(runtime, "MATRIX_ACCOUNTS");
17
+ if (!raw)
18
+ return {};
19
+ try {
20
+ const parsed = JSON.parse(raw);
21
+ if (Array.isArray(parsed)) {
22
+ return Object.fromEntries(parsed.filter((item) => Boolean(item) && typeof item === "object").map((item) => [normalizeMatrixAccountId(item.accountId ?? item.id), item]));
23
+ }
24
+ return parsed && typeof parsed === "object" ? parsed : {};
25
+ } catch {
26
+ return {};
27
+ }
28
+ }
29
+ function allAccountConfigs(runtime) {
30
+ return {
31
+ ...characterConfig(runtime).accounts ?? {},
32
+ ...parseAccountsJson(runtime)
33
+ };
34
+ }
35
+ function accountConfig(runtime, accountId) {
36
+ const accounts = allAccountConfigs(runtime);
37
+ return accounts[accountId] ?? accounts[normalizeMatrixAccountId(accountId)] ?? {};
38
+ }
39
+ function boolValue(value, fallback = false) {
40
+ if (typeof value === "boolean")
41
+ return value;
42
+ if (typeof value === "string")
43
+ return value.trim().toLowerCase() === "true";
44
+ return fallback;
45
+ }
46
+ function roomsValue(value) {
47
+ if (Array.isArray(value)) {
48
+ return value.map((room) => String(room).trim()).filter(Boolean);
49
+ }
50
+ if (typeof value === "string") {
51
+ return value.split(",").map((room) => room.trim()).filter(Boolean);
52
+ }
53
+ return [];
54
+ }
55
+ function normalizeMatrixAccountId(accountId) {
56
+ if (typeof accountId !== "string")
57
+ return DEFAULT_MATRIX_ACCOUNT_ID;
58
+ const trimmed = accountId.trim();
59
+ return trimmed || DEFAULT_MATRIX_ACCOUNT_ID;
60
+ }
61
+ function listMatrixAccountIds(runtime) {
62
+ const ids = new Set;
63
+ const config = characterConfig(runtime);
64
+ if (stringSetting(runtime, "MATRIX_ACCESS_TOKEN") || config.homeserver && config.userId && config.accessToken) {
65
+ ids.add(DEFAULT_MATRIX_ACCOUNT_ID);
66
+ }
67
+ for (const id of Object.keys(allAccountConfigs(runtime))) {
68
+ ids.add(normalizeMatrixAccountId(id));
69
+ }
70
+ return Array.from(ids.size ? ids : new Set([DEFAULT_MATRIX_ACCOUNT_ID])).sort((a, b) => a.localeCompare(b));
71
+ }
72
+ function resolveDefaultMatrixAccountId(runtime) {
73
+ const requested = stringSetting(runtime, "MATRIX_DEFAULT_ACCOUNT_ID") ?? stringSetting(runtime, "MATRIX_ACCOUNT_ID");
74
+ if (requested)
75
+ return normalizeMatrixAccountId(requested);
76
+ const ids = listMatrixAccountIds(runtime);
77
+ return ids.includes(DEFAULT_MATRIX_ACCOUNT_ID) ? DEFAULT_MATRIX_ACCOUNT_ID : ids[0];
78
+ }
79
+ function readMatrixAccountId(...sources) {
80
+ for (const source of sources) {
81
+ if (!source || typeof source !== "object")
82
+ continue;
83
+ const record = source;
84
+ const parameters = record.parameters && typeof record.parameters === "object" ? record.parameters : {};
85
+ const data = record.data && typeof record.data === "object" ? record.data : {};
86
+ const metadata = record.metadata && typeof record.metadata === "object" ? record.metadata : {};
87
+ const matrix = data.matrix && typeof data.matrix === "object" ? data.matrix : {};
88
+ const value = record.accountId ?? parameters.accountId ?? data.accountId ?? matrix.accountId ?? metadata.accountId;
89
+ if (typeof value === "string" && value.trim())
90
+ return normalizeMatrixAccountId(value);
91
+ }
92
+ return;
93
+ }
94
+ function resolveMatrixAccountSettings(runtime, requestedAccountId) {
95
+ const accountId = normalizeMatrixAccountId(requestedAccountId ?? resolveDefaultMatrixAccountId(runtime));
96
+ const base = characterConfig(runtime);
97
+ const account = accountConfig(runtime, accountId);
98
+ const allowEnv = accountId === DEFAULT_MATRIX_ACCOUNT_ID;
99
+ return {
100
+ accountId,
101
+ homeserver: account.homeserver ?? base.homeserver ?? (allowEnv ? stringSetting(runtime, "MATRIX_HOMESERVER") : undefined) ?? "",
102
+ userId: account.userId ?? base.userId ?? (allowEnv ? stringSetting(runtime, "MATRIX_USER_ID") : undefined) ?? "",
103
+ accessToken: account.accessToken ?? base.accessToken ?? (allowEnv ? stringSetting(runtime, "MATRIX_ACCESS_TOKEN") : undefined) ?? "",
104
+ deviceId: account.deviceId ?? base.deviceId ?? (allowEnv ? stringSetting(runtime, "MATRIX_DEVICE_ID") : undefined),
105
+ rooms: roomsValue(account.rooms ?? base.rooms ?? (allowEnv ? stringSetting(runtime, "MATRIX_ROOMS") : undefined)),
106
+ autoJoin: boolValue(account.autoJoin ?? base.autoJoin ?? (allowEnv ? stringSetting(runtime, "MATRIX_AUTO_JOIN") : undefined)),
107
+ encryption: boolValue(account.encryption ?? base.encryption ?? (allowEnv ? stringSetting(runtime, "MATRIX_ENCRYPTION") : undefined)),
108
+ requireMention: boolValue(account.requireMention ?? base.requireMention ?? (allowEnv ? stringSetting(runtime, "MATRIX_REQUIRE_MENTION") : undefined)),
109
+ enabled: boolValue(account.enabled ?? base.enabled, true)
110
+ };
111
+ }
4
112
  // src/service.ts
5
- import { logger, Service } from "@elizaos/core";
113
+ import {
114
+ logger,
115
+ Service
116
+ } from "@elizaos/core";
6
117
  import * as sdk from "matrix-js-sdk";
7
118
 
8
119
  // src/types.ts
@@ -93,13 +204,94 @@ class MatrixApiError extends MatrixPluginError {
93
204
  }
94
205
 
95
206
  // src/service.ts
207
+ function normalizeSearchQuery(query) {
208
+ return query.trim().toLowerCase();
209
+ }
210
+ function matrixRoomSearchText(room) {
211
+ return [room.roomId, room.name, room.topic, room.canonicalAlias].filter((value) => typeof value === "string" && value.length > 0).join(" ").toLowerCase();
212
+ }
213
+ function scoreMatrixRoom(room, query) {
214
+ const normalized = normalizeSearchQuery(query);
215
+ if (!normalized) {
216
+ return 0.4;
217
+ }
218
+ const candidates = [room.roomId, room.canonicalAlias, room.name].filter((value) => typeof value === "string" && value.length > 0);
219
+ if (candidates.some((candidate) => candidate.toLowerCase() === normalized)) {
220
+ return 1;
221
+ }
222
+ if (candidates.some((candidate) => candidate.toLowerCase().includes(normalized))) {
223
+ return 0.85;
224
+ }
225
+ return matrixRoomSearchText(room).includes(normalized) ? 0.65 : 0;
226
+ }
227
+ function matrixRoomToConnectorTarget(room, score = 0.5, accountId = DEFAULT_MATRIX_ACCOUNT_ID) {
228
+ const label = room.name || room.canonicalAlias || room.roomId;
229
+ return {
230
+ target: {
231
+ source: MATRIX_SERVICE_NAME,
232
+ accountId,
233
+ channelId: room.roomId
234
+ },
235
+ label,
236
+ kind: room.isDirect ? "user" : "room",
237
+ description: room.topic || `${room.memberCount} Matrix member${room.memberCount === 1 ? "" : "s"}`,
238
+ score,
239
+ contexts: ["social", "connectors"],
240
+ metadata: {
241
+ accountId,
242
+ roomId: room.roomId,
243
+ canonicalAlias: room.canonicalAlias,
244
+ isEncrypted: room.isEncrypted,
245
+ isDirect: room.isDirect,
246
+ memberCount: room.memberCount
247
+ }
248
+ };
249
+ }
250
+ function normalizeConnectorLimit(limit, fallback = 50) {
251
+ if (!Number.isFinite(limit) || !limit || limit <= 0) {
252
+ return fallback;
253
+ }
254
+ return Math.min(Math.floor(limit), 200);
255
+ }
256
+ async function readStoredMessageMemories(runtime, roomId, limit) {
257
+ return runtime.getMemories({
258
+ tableName: "messages",
259
+ roomId,
260
+ limit,
261
+ orderBy: "createdAt",
262
+ orderDirection: "desc"
263
+ });
264
+ }
265
+ async function readStoredMessagesForTargets(runtime, targets, limit) {
266
+ const roomIds = Array.from(new Set(targets.map((target) => target.target.roomId).filter((id) => Boolean(id))));
267
+ const chunks = await Promise.all(roomIds.map((roomId) => readStoredMessageMemories(runtime, roomId, limit)));
268
+ return chunks.flat().sort((left, right) => (right.createdAt ?? 0) - (left.createdAt ?? 0)).slice(0, limit);
269
+ }
270
+ function filterMemoriesByQuery(memories, query, limit) {
271
+ const normalized = query.trim().toLowerCase();
272
+ if (!normalized) {
273
+ return memories.slice(0, limit);
274
+ }
275
+ return memories.filter((memory) => {
276
+ const text = typeof memory.content?.text === "string" ? memory.content.text : "";
277
+ return text.toLowerCase().includes(normalized);
278
+ }).slice(0, limit);
279
+ }
280
+ function extractMatrixSendOptions(content, target) {
281
+ const data = content.data;
282
+ const matrixData = data?.matrix && typeof data.matrix === "object" ? data.matrix : data;
283
+ return {
284
+ threadId: target.threadId || (typeof matrixData?.threadId === "string" ? matrixData.threadId : undefined),
285
+ replyTo: typeof matrixData?.replyTo === "string" ? matrixData.replyTo : undefined,
286
+ formatted: matrixData?.formatted === true
287
+ };
288
+ }
289
+
96
290
  class MatrixService extends Service {
97
291
  static serviceType = MATRIX_SERVICE_NAME;
98
292
  capabilityDescription = "Matrix messaging service for chat communication";
99
- settings;
100
- client;
101
- connected = false;
102
- syncing = false;
293
+ states = new Map;
294
+ defaultAccountId = DEFAULT_MATRIX_ACCOUNT_ID;
103
295
  static async start(runtime) {
104
296
  const service = new MatrixService;
105
297
  await service.initialize(runtime);
@@ -111,91 +303,216 @@ class MatrixService extends Service {
111
303
  await service.stop();
112
304
  }
113
305
  }
306
+ static registerSendHandlers(runtime, service, accountId = service.getAccountId(runtime)) {
307
+ accountId = normalizeMatrixAccountId(accountId);
308
+ const sendHandler = async (handlerRuntime, target, content) => {
309
+ await service.handleSendMessage(handlerRuntime, target, content);
310
+ return;
311
+ };
312
+ if (typeof runtime.registerMessageConnector === "function") {
313
+ const registration = {
314
+ source: MATRIX_SERVICE_NAME,
315
+ accountId,
316
+ label: "Matrix",
317
+ capabilities: [
318
+ "send_message",
319
+ "send_thread_reply",
320
+ "send_formatted_message",
321
+ "react_to_message",
322
+ "list_rooms",
323
+ "join_room"
324
+ ],
325
+ supportedTargetKinds: ["room", "channel", "thread", "user"],
326
+ contexts: ["social", "connectors"],
327
+ description: "Send messages to joined Matrix rooms, aliases, encrypted rooms, and known direct-message rooms.",
328
+ metadata: {
329
+ accountId,
330
+ service: MATRIX_SERVICE_NAME
331
+ },
332
+ sendHandler,
333
+ resolveTargets: async (query) => {
334
+ const rooms = await service.getJoinedRooms(accountId);
335
+ return rooms.map((room) => ({ room, score: scoreMatrixRoom(room, query) })).filter(({ score }) => score > 0).sort((left, right) => right.score - left.score).slice(0, 10).map(({ room, score }) => matrixRoomToConnectorTarget(room, score, accountId));
336
+ },
337
+ listRecentTargets: async () => (await service.getJoinedRooms(accountId)).slice(0, 10).map((room) => matrixRoomToConnectorTarget(room, 0.5, accountId)),
338
+ listRooms: async () => (await service.getJoinedRooms(accountId)).map((room) => matrixRoomToConnectorTarget(room, 0.5, accountId)),
339
+ fetchMessages: async (context, params) => {
340
+ const limit = normalizeConnectorLimit(params?.limit);
341
+ const target = params?.target ?? context.target;
342
+ if (target?.roomId) {
343
+ return readStoredMessageMemories(context.runtime, target.roomId, limit);
344
+ }
345
+ const targets = (await service.getJoinedRooms(accountId)).slice(0, 10).map((room) => matrixRoomToConnectorTarget(room, 0.5, accountId));
346
+ return readStoredMessagesForTargets(context.runtime, targets, limit);
347
+ },
348
+ searchMessages: async (context, params) => {
349
+ const limit = normalizeConnectorLimit(params?.limit);
350
+ const target = params?.target ?? context.target;
351
+ const messages = target?.roomId ? await readStoredMessageMemories(context.runtime, target.roomId, Math.max(limit, 100)) : await readStoredMessagesForTargets(context.runtime, (await service.getJoinedRooms(accountId)).slice(0, 10).map((room) => matrixRoomToConnectorTarget(room, 0.5, accountId)), Math.max(limit, 100));
352
+ return filterMemoriesByQuery(messages, params.query, limit);
353
+ },
354
+ reactHandler: async (handlerRuntime, params) => {
355
+ const target = params.target ?? { source: MATRIX_SERVICE_NAME };
356
+ const room = target.roomId ? await handlerRuntime.getRoom(target.roomId) : null;
357
+ const roomId = String(target.channelId ?? room?.channelId ?? "").trim();
358
+ const mutationParams = params;
359
+ const eventId = String(mutationParams.eventId ?? params.messageId ?? "").trim();
360
+ const emoji = String(params.emoji ?? "").trim();
361
+ if (!roomId || !eventId || !emoji) {
362
+ throw new Error("Matrix reactHandler requires room, event id, and emoji");
363
+ }
364
+ const result = await service.sendReaction(roomId, eventId, emoji, accountId);
365
+ if (!result.success) {
366
+ throw new Error(result.error || "Matrix reaction failed");
367
+ }
368
+ },
369
+ joinHandler: async (_handlerRuntime, params) => {
370
+ const membershipParams = params;
371
+ const roomIdOrAlias = String(membershipParams.roomIdOrAlias ?? params.alias ?? params.invite ?? params.channelId ?? params.roomId ?? "").trim();
372
+ if (!roomIdOrAlias) {
373
+ throw new Error("Matrix joinHandler requires a room ID or alias");
374
+ }
375
+ await service.joinRoom(roomIdOrAlias, accountId);
376
+ },
377
+ leaveHandler: async (handlerRuntime, params) => {
378
+ const target = params.target ?? { source: MATRIX_SERVICE_NAME };
379
+ const room = target.roomId ? await handlerRuntime.getRoom(target.roomId) : null;
380
+ const roomId = String(params?.roomId ?? params?.channelId ?? target.channelId ?? room?.channelId ?? "");
381
+ if (!roomId) {
382
+ throw new Error("Matrix leaveHandler requires a room ID");
383
+ }
384
+ await service.leaveRoom(roomId, accountId);
385
+ },
386
+ getChatContext: async (target, context) => {
387
+ const room = target.roomId ? await context.runtime.getRoom(target.roomId) : null;
388
+ const channelId = String(target.channelId ?? room?.channelId ?? "").trim();
389
+ const joinedRoom = (await service.getJoinedRooms(accountId)).find((candidate) => candidate.roomId === channelId || candidate.canonicalAlias === channelId);
390
+ if (!joinedRoom) {
391
+ return null;
392
+ }
393
+ return {
394
+ target: {
395
+ source: MATRIX_SERVICE_NAME,
396
+ accountId,
397
+ channelId: joinedRoom.roomId,
398
+ roomId: target.roomId
399
+ },
400
+ label: joinedRoom.name || joinedRoom.canonicalAlias || joinedRoom.roomId,
401
+ summary: joinedRoom.topic,
402
+ metadata: {
403
+ accountId,
404
+ roomId: joinedRoom.roomId,
405
+ canonicalAlias: joinedRoom.canonicalAlias,
406
+ isEncrypted: joinedRoom.isEncrypted,
407
+ isDirect: joinedRoom.isDirect,
408
+ memberCount: joinedRoom.memberCount
409
+ }
410
+ };
411
+ },
412
+ getUserContext: async (entityId, context) => {
413
+ if (typeof context.runtime.getEntityById !== "function") {
414
+ return null;
415
+ }
416
+ const entity = await context.runtime.getEntityById(String(entityId));
417
+ if (!entity) {
418
+ return null;
419
+ }
420
+ return {
421
+ entityId,
422
+ label: entity.names?.[0],
423
+ aliases: entity.names,
424
+ handles: {},
425
+ metadata: entity.metadata
426
+ };
427
+ }
428
+ };
429
+ runtime.registerMessageConnector(registration);
430
+ return;
431
+ }
432
+ runtime.registerSendHandler(MATRIX_SERVICE_NAME, sendHandler);
433
+ }
114
434
  async initialize(runtime) {
115
435
  this.runtime = runtime;
116
- this.settings = this.loadSettings();
117
- this.validateSettings();
118
- this.client = sdk.createClient({
119
- baseUrl: this.settings.homeserver,
120
- userId: this.settings.userId,
121
- accessToken: this.settings.accessToken,
122
- deviceId: this.settings.deviceId
123
- });
124
- this.setupEventHandlers();
125
- await this.connect();
126
- logger.info(`Matrix service initialized for ${this.settings.userId} on ${this.settings.homeserver}`);
436
+ this.defaultAccountId = normalizeMatrixAccountId(resolveDefaultMatrixAccountId(runtime));
437
+ const accountIds = listMatrixAccountIds(runtime);
438
+ for (const accountId of accountIds) {
439
+ const settings = this.loadSettings(accountId);
440
+ if (settings.enabled === false) {
441
+ continue;
442
+ }
443
+ this.validateSettings(settings);
444
+ const state = {
445
+ accountId: normalizeMatrixAccountId(settings.accountId),
446
+ settings,
447
+ client: sdk.createClient({
448
+ baseUrl: settings.homeserver,
449
+ userId: settings.userId,
450
+ accessToken: settings.accessToken,
451
+ deviceId: settings.deviceId
452
+ }),
453
+ connected: false,
454
+ syncing: false
455
+ };
456
+ this.states.set(state.accountId, state);
457
+ this.setupEventHandlers(state);
458
+ await this.connect(state);
459
+ MatrixService.registerSendHandlers(runtime, this, state.accountId);
460
+ logger.info(`Matrix service initialized for ${settings.userId} on ${settings.homeserver}`);
461
+ }
462
+ if (this.states.size === 0) {
463
+ const settings = this.loadSettings(this.defaultAccountId);
464
+ this.validateSettings(settings);
465
+ }
127
466
  }
128
- loadSettings() {
129
- const getStringSetting = (key) => {
130
- const value = this.runtime.getSetting(key);
131
- return typeof value === "string" ? value : undefined;
132
- };
133
- const homeserver = getStringSetting("MATRIX_HOMESERVER");
134
- const userId = getStringSetting("MATRIX_USER_ID");
135
- const accessToken = getStringSetting("MATRIX_ACCESS_TOKEN");
136
- const deviceId = getStringSetting("MATRIX_DEVICE_ID");
137
- const roomsStr = getStringSetting("MATRIX_ROOMS");
138
- const autoJoinStr = getStringSetting("MATRIX_AUTO_JOIN");
139
- const encryptionStr = getStringSetting("MATRIX_ENCRYPTION");
140
- const requireMentionStr = getStringSetting("MATRIX_REQUIRE_MENTION");
141
- const rooms = roomsStr ? roomsStr.split(",").map((r) => r.trim()).filter(Boolean) : [];
142
- return {
143
- homeserver: homeserver || "",
144
- userId: userId || "",
145
- accessToken: accessToken || "",
146
- deviceId,
147
- rooms,
148
- autoJoin: autoJoinStr === "true",
149
- encryption: encryptionStr === "true",
150
- requireMention: requireMentionStr === "true",
151
- enabled: true
152
- };
467
+ loadSettings(accountId) {
468
+ return resolveMatrixAccountSettings(this.runtime, accountId);
153
469
  }
154
- validateSettings() {
155
- if (!this.settings.homeserver) {
470
+ validateSettings(settings) {
471
+ if (!settings.homeserver) {
156
472
  throw new MatrixConfigurationError("MATRIX_HOMESERVER is required", "MATRIX_HOMESERVER");
157
473
  }
158
- if (!this.settings.userId) {
474
+ if (!settings.userId) {
159
475
  throw new MatrixConfigurationError("MATRIX_USER_ID is required", "MATRIX_USER_ID");
160
476
  }
161
- if (!this.settings.accessToken) {
477
+ if (!settings.accessToken) {
162
478
  throw new MatrixConfigurationError("MATRIX_ACCESS_TOKEN is required", "MATRIX_ACCESS_TOKEN");
163
479
  }
164
480
  }
165
- setupEventHandlers() {
166
- this.client.on(sdk.ClientEvent.Sync, (state) => {
167
- if (state === "PREPARED") {
168
- this.syncing = true;
481
+ setupEventHandlers(state) {
482
+ state.client.on(sdk.ClientEvent.Sync, (syncState) => {
483
+ if (syncState === "PREPARED") {
484
+ state.syncing = true;
169
485
  logger.info("Matrix sync complete");
170
486
  this.runtime.emitEvent("MATRIX_SYNC_COMPLETE" /* SYNC_COMPLETE */, {
171
- runtime: this.runtime
487
+ runtime: this.runtime,
488
+ accountId: state.accountId
172
489
  });
173
490
  }
174
491
  });
175
- this.client.on(sdk.RoomEvent.Timeline, (event, room, toStartOfTimeline) => {
492
+ state.client.on(sdk.RoomEvent.Timeline, (event, room, toStartOfTimeline) => {
176
493
  if (toStartOfTimeline)
177
494
  return;
178
495
  if (event.getType() !== "m.room.message")
179
496
  return;
180
- if (event.getSender() === this.settings.userId)
497
+ if (event.getSender() === state.settings.userId)
181
498
  return;
182
- this.handleRoomMessage(event, room);
499
+ this.handleRoomMessage(state, event, room);
183
500
  });
184
- this.client.on(sdk.RoomMemberEvent.Membership, (event, member) => {
185
- if (member.userId !== this.settings.userId)
501
+ state.client.on(sdk.RoomMemberEvent.Membership, (event, member) => {
502
+ if (member.userId !== state.settings.userId)
186
503
  return;
187
- if (member.membership === "invite" && this.settings.autoJoin) {
504
+ if (member.membership === "invite" && state.settings.autoJoin) {
188
505
  const roomId = event.getRoomId();
189
506
  if (roomId) {
190
507
  logger.info(`Auto-joining room ${roomId}`);
191
- this.client.joinRoom(roomId).catch((err) => {
508
+ state.client.joinRoom(roomId).catch((err) => {
192
509
  logger.error(`Failed to auto-join room: ${err.message}`);
193
510
  });
194
511
  }
195
512
  }
196
513
  });
197
514
  }
198
- handleRoomMessage(event, room) {
515
+ handleRoomMessage(state, event, room) {
199
516
  const content = event.getContent();
200
517
  const msgType = content.msgtype;
201
518
  if (msgType !== "m.text")
@@ -203,9 +520,9 @@ class MatrixService extends Service {
203
520
  const roomId = event.getRoomId();
204
521
  if (!roomId || !room)
205
522
  return;
206
- if (this.settings.requireMention) {
523
+ if (state.settings.requireMention) {
207
524
  const body = content.body || "";
208
- const localpart = getMatrixLocalpart(this.settings.userId);
525
+ const localpart = getMatrixLocalpart(state.settings.userId);
209
526
  const mentionPattern = new RegExp(`@?${localpart}`, "i");
210
527
  if (!mentionPattern.test(body)) {
211
528
  return;
@@ -242,54 +559,70 @@ class MatrixService extends Service {
242
559
  topic: room.currentState.getStateEvents("m.room.topic", "")?.getContent()?.topic,
243
560
  canonicalAlias: room.getCanonicalAlias() || undefined,
244
561
  isEncrypted: room.hasEncryptionStateEvent(),
245
- isDirect: this.client.getAccountData("m.direct")?.getContent()?.[sender || ""]?.includes(roomId) || false,
562
+ isDirect: state.client.getAccountData(sdk.EventType.Direct)?.getContent()?.[sender || ""]?.includes(roomId) || false,
246
563
  memberCount: room.getJoinedMemberCount()
247
564
  };
248
565
  logger.debug(`Matrix message from ${senderInfo.displayName || sender} in ${room.name || roomId}: ${message.content.slice(0, 50)}...`);
249
566
  this.runtime.emitEvent("MATRIX_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, {
250
567
  message,
251
568
  room: matrixRoom,
252
- runtime: this.runtime
569
+ runtime: this.runtime,
570
+ accountId: state.accountId
253
571
  });
254
572
  }
255
- async connect() {
256
- await this.client.startClient({ initialSyncLimit: 10 });
257
- this.connected = true;
573
+ async connect(state) {
574
+ await state.client.startClient({ initialSyncLimit: 10 });
575
+ state.connected = true;
258
576
  await new Promise((resolve) => {
259
- const listener = (state) => {
260
- if (state === "PREPARED") {
261
- this.client.removeListener(sdk.ClientEvent.Sync, listener);
577
+ const listener = (syncState) => {
578
+ if (syncState === "PREPARED") {
579
+ state.client.removeListener(sdk.ClientEvent.Sync, listener);
262
580
  resolve();
263
581
  }
264
582
  };
265
- this.client.on(sdk.ClientEvent.Sync, listener);
583
+ state.client.on(sdk.ClientEvent.Sync, listener);
266
584
  });
267
- for (const room of this.settings.rooms) {
585
+ for (const room of state.settings.rooms) {
268
586
  try {
269
- await this.joinRoom(room);
587
+ await this.joinRoom(room, state.accountId);
270
588
  } catch (err) {
271
589
  logger.warn(`Failed to join room ${room}: ${err}`);
272
590
  }
273
591
  }
274
592
  }
275
593
  async stop() {
276
- if (this.client) {
277
- this.client.stopClient();
594
+ for (const state of this.states.values()) {
595
+ state.client.stopClient();
596
+ state.connected = false;
597
+ state.syncing = false;
278
598
  }
279
- this.connected = false;
280
599
  logger.info("Matrix service stopped");
281
600
  }
282
601
  isConnected() {
283
- return this.connected && this.syncing;
602
+ const legacy = this;
603
+ const states = this.states ?? new Map;
604
+ if (states.size === 0 && typeof legacy.connected === "boolean") {
605
+ return legacy.connected && (legacy.syncing ?? true);
606
+ }
607
+ return Array.from(states.values()).some((state) => state.connected && state.syncing);
608
+ }
609
+ getAccountId(runtime) {
610
+ const legacy = this;
611
+ const states = this.states ?? new Map;
612
+ if (states.size === 0 && legacy.settings?.accountId) {
613
+ return normalizeMatrixAccountId(legacy.settings.accountId);
614
+ }
615
+ return normalizeMatrixAccountId(this.defaultAccountId !== DEFAULT_MATRIX_ACCOUNT_ID ? this.defaultAccountId : runtime ? resolveDefaultMatrixAccountId(runtime) : this.defaultAccountId);
284
616
  }
285
617
  getUserId() {
286
- return this.settings.userId;
618
+ return this.getState().settings.userId;
287
619
  }
288
620
  getHomeserver() {
289
- return this.settings.homeserver;
621
+ return this.getState().settings.homeserver;
290
622
  }
291
- async getJoinedRooms() {
292
- const rooms = this.client.getRooms();
623
+ async getJoinedRooms(accountId) {
624
+ const state = this.getState(accountId);
625
+ const rooms = state.client.getRooms();
293
626
  return rooms.filter((room) => room.getMyMembership() === "join").map((room) => ({
294
627
  roomId: room.roomId,
295
628
  name: room.name,
@@ -301,7 +634,8 @@ class MatrixService extends Service {
301
634
  }));
302
635
  }
303
636
  async sendMessage(text, options) {
304
- if (!this.isConnected()) {
637
+ const state = this.getState(options?.accountId);
638
+ if (!state.connected || !state.syncing) {
305
639
  throw new MatrixNotConnectedError;
306
640
  }
307
641
  const roomId = options?.roomId;
@@ -310,11 +644,11 @@ class MatrixService extends Service {
310
644
  }
311
645
  let resolvedRoomId = roomId;
312
646
  if (isValidMatrixRoomAlias(roomId)) {
313
- const resolved = await this.client.getRoomIdForAlias(roomId);
647
+ const resolved = await state.client.getRoomIdForAlias(roomId);
314
648
  resolvedRoomId = resolved.room_id;
315
649
  }
316
650
  const content = {
317
- msgtype: "m.text",
651
+ msgtype: sdk.MsgType.Text,
318
652
  body: text
319
653
  };
320
654
  if (options?.formatted) {
@@ -324,7 +658,7 @@ class MatrixService extends Service {
324
658
  if (options?.threadId || options?.replyTo) {
325
659
  content["m.relates_to"] = {};
326
660
  if (options.threadId) {
327
- content["m.relates_to"].rel_type = "m.thread";
661
+ content["m.relates_to"].rel_type = sdk.RelationType.Thread;
328
662
  content["m.relates_to"].event_id = options.threadId;
329
663
  }
330
664
  if (options.replyTo) {
@@ -333,13 +667,14 @@ class MatrixService extends Service {
333
667
  };
334
668
  }
335
669
  }
336
- const response = await this.client.sendMessage(resolvedRoomId, content);
670
+ const response = await state.client.sendMessage(resolvedRoomId, content);
337
671
  const eventId = response.event_id;
338
672
  this.runtime.emitEvent("MATRIX_MESSAGE_SENT" /* MESSAGE_SENT */, {
339
673
  roomId: resolvedRoomId,
340
674
  eventId,
341
675
  content: text,
342
- runtime: this.runtime
676
+ runtime: this.runtime,
677
+ accountId: state.accountId
343
678
  });
344
679
  return {
345
680
  success: true,
@@ -347,618 +682,233 @@ class MatrixService extends Service {
347
682
  roomId: resolvedRoomId
348
683
  };
349
684
  }
350
- async sendReaction(roomId, eventId, emoji) {
351
- if (!this.isConnected()) {
685
+ async sendReaction(roomId, eventId, emoji, accountId) {
686
+ const state = this.getState(accountId);
687
+ if (!state.connected || !state.syncing) {
352
688
  throw new MatrixNotConnectedError;
353
689
  }
354
690
  const content = {
355
691
  "m.relates_to": {
356
- rel_type: "m.annotation",
692
+ rel_type: sdk.RelationType.Annotation,
357
693
  event_id: eventId,
358
694
  key: emoji
359
695
  }
360
696
  };
361
- const response = await this.client.sendEvent(roomId, "m.reaction", content);
697
+ const response = await state.client.sendEvent(roomId, sdk.EventType.Reaction, content);
362
698
  return {
363
699
  success: true,
364
700
  eventId: response.event_id,
365
701
  roomId
366
702
  };
367
703
  }
368
- async joinRoom(roomIdOrAlias) {
369
- if (!this.isConnected()) {
704
+ async joinRoom(roomIdOrAlias, accountId) {
705
+ const state = this.getState(accountId);
706
+ if (!state.connected || !state.syncing) {
370
707
  throw new MatrixNotConnectedError;
371
708
  }
372
- const response = await this.client.joinRoom(roomIdOrAlias);
709
+ const response = await state.client.joinRoom(roomIdOrAlias);
373
710
  const roomId = response.roomId;
374
711
  logger.info(`Joined room ${roomId}`);
375
712
  this.runtime.emitEvent("MATRIX_ROOM_JOINED" /* ROOM_JOINED */, {
376
713
  room: { roomId },
377
- runtime: this.runtime
714
+ runtime: this.runtime,
715
+ accountId: state.accountId
378
716
  });
379
717
  return roomId;
380
718
  }
381
- async leaveRoom(roomId) {
382
- if (!this.isConnected()) {
719
+ async leaveRoom(roomId, accountId) {
720
+ const state = this.getState(accountId);
721
+ if (!state.connected || !state.syncing) {
383
722
  throw new MatrixNotConnectedError;
384
723
  }
385
- await this.client.leave(roomId);
724
+ await state.client.leave(roomId);
386
725
  logger.info(`Left room ${roomId}`);
387
726
  this.runtime.emitEvent("MATRIX_ROOM_LEFT" /* ROOM_LEFT */, {
388
727
  roomId,
389
- runtime: this.runtime
728
+ runtime: this.runtime,
729
+ accountId: state.accountId
390
730
  });
391
731
  }
392
- async sendTyping(roomId, typing, timeout = 30000) {
393
- if (!this.isConnected()) {
732
+ async sendTyping(roomId, typing, timeout = 30000, accountId) {
733
+ const state = this.getState(accountId);
734
+ if (!state.connected || !state.syncing) {
394
735
  return;
395
736
  }
396
- await this.client.sendTyping(roomId, typing, timeout);
737
+ await state.client.sendTyping(roomId, typing, timeout);
397
738
  }
398
- async sendReadReceipt(roomId, eventId) {
399
- if (!this.isConnected()) {
739
+ async sendReadReceipt(roomId, eventId, accountId) {
740
+ const state = this.getState(accountId);
741
+ if (!state.connected || !state.syncing) {
400
742
  return;
401
743
  }
402
- await this.client.sendReadReceipt(new sdk.MatrixEvent({ event_id: eventId, room_id: roomId }));
744
+ await state.client.sendReadReceipt(new sdk.MatrixEvent({ event_id: eventId, room_id: roomId }));
403
745
  }
404
- }
405
- // src/actions/joinRoom.ts
406
- import { composePromptFromState, ModelType, parseJSONObjectFromText } from "@elizaos/core";
407
- var JOIN_ROOM_TEMPLATE = `You are helping to extract a Matrix room identifier.
408
-
409
- The user wants to join a Matrix room.
410
-
411
- Recent conversation:
412
- {{recentMessages}}
413
-
414
- Extract the room ID (!room:server) or room alias (#alias:server) to join.
415
-
416
- Respond with a JSON object like:
417
- {
418
- "room": "!room:matrix.org"
419
- }
420
-
421
- or:
422
-
423
- {
424
- "room": "#alias:matrix.org"
425
- }
426
-
427
- Only respond with the JSON object, no other text.`;
428
- var joinRoom = {
429
- name: "MATRIX_JOIN_ROOM",
430
- similes: ["JOIN_MATRIX_ROOM", "ENTER_ROOM"],
431
- description: "Join a Matrix room by ID or alias",
432
- validate: async (_runtime, message, _state) => {
433
- return message.content.source === "matrix";
434
- },
435
- handler: async (runtime, message, state, _options, callback) => {
436
- const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
437
- if (!matrixService || !matrixService.isConnected()) {
438
- if (callback) {
439
- await callback({ text: "Matrix service is not available.", source: "matrix" });
440
- }
441
- return { success: false, error: "Matrix service not available" };
746
+ async sendRoomMessage(roomIdOrAlias, content) {
747
+ const text = typeof content.text === "string" ? content.text.trim() : "";
748
+ if (!text) {
749
+ return;
442
750
  }
443
- const composedState = state ?? {
444
- values: {},
445
- data: {},
446
- text: ""
447
- };
448
- const prompt = await composePromptFromState({
449
- template: JOIN_ROOM_TEMPLATE,
450
- state: composedState
751
+ await this.sendMessage(text, {
752
+ accountId: readMatrixAccountId(content) ?? this.getAccountId(),
753
+ roomId: roomIdOrAlias
451
754
  });
452
- let room = null;
453
- for (let attempt = 0;attempt < 3; attempt++) {
454
- const response = await runtime.useModel(ModelType.TEXT_SMALL, {
455
- prompt
456
- });
457
- const parsed = parseJSONObjectFromText(response);
458
- if (parsed?.room) {
459
- const roomStr = String(parsed.room).trim();
460
- if (isValidMatrixRoomId(roomStr) || isValidMatrixRoomAlias(roomStr)) {
461
- room = roomStr;
462
- break;
463
- }
464
- }
755
+ }
756
+ async sendDirectMessage(roomIdOrAlias, content) {
757
+ await this.sendRoomMessage(roomIdOrAlias, content);
758
+ }
759
+ async handleSendMessage(runtime, target, content) {
760
+ const requestedAccountId = normalizeMatrixAccountId(target.accountId ?? readMatrixAccountId(content, target) ?? this.getAccountId());
761
+ this.getState(requestedAccountId);
762
+ const text = typeof content.text === "string" ? content.text.trim() : "";
763
+ if (!text) {
764
+ return;
465
765
  }
466
- if (!room) {
467
- if (callback) {
468
- await callback({
469
- text: "I couldn't understand which room you want me to join. Please specify a room ID (!room:server) or alias (#alias:server).",
470
- source: "matrix"
471
- });
472
- }
473
- return { success: false, error: "Could not extract room identifier" };
766
+ const room = target.roomId ? await runtime.getRoom(target.roomId) : null;
767
+ const roomIdOrAlias = String(target.channelId || room?.channelId || (typeof target.roomId === "string" && (isValidMatrixRoomId(target.roomId) || isValidMatrixRoomAlias(target.roomId)) ? target.roomId : "")).trim();
768
+ if (!roomIdOrAlias) {
769
+ throw new Error("Matrix target is missing a room ID or alias");
474
770
  }
475
- try {
476
- const roomId = await matrixService.joinRoom(room);
477
- if (callback) {
478
- await callback({
479
- text: `Joined room ${room}.`,
480
- source: message.content.source
481
- });
482
- }
771
+ await this.sendMessage(text, {
772
+ accountId: requestedAccountId,
773
+ roomId: roomIdOrAlias,
774
+ ...extractMatrixSendOptions(content, target)
775
+ });
776
+ }
777
+ getState(accountId = this.defaultAccountId) {
778
+ const normalized = normalizeMatrixAccountId(accountId);
779
+ const states = this.states ?? new Map;
780
+ const state = states.get(normalized);
781
+ if (state) {
782
+ return state;
783
+ }
784
+ const legacy = this;
785
+ if (legacy.settings) {
483
786
  return {
484
- success: true,
485
- data: {
486
- roomId,
487
- joined: room
488
- }
787
+ accountId: normalizeMatrixAccountId(legacy.settings.accountId ?? normalized),
788
+ settings: legacy.settings,
789
+ client: legacy.client ?? {},
790
+ connected: legacy.connected ?? true,
791
+ syncing: legacy.syncing ?? true
489
792
  };
490
- } catch (err) {
491
- const error = err instanceof Error ? err.message : String(err);
492
- if (callback) {
493
- await callback({
494
- text: `Failed to join room: ${error}`,
495
- source: "matrix"
496
- });
497
- }
498
- return { success: false, error };
499
793
  }
500
- },
501
- examples: [
502
- [
503
- {
504
- name: "{{user1}}",
505
- content: { text: "Join #general:matrix.org" }
506
- },
507
- {
508
- name: "{{agent}}",
509
- content: {
510
- text: "I'll join that room.",
511
- actions: ["MATRIX_JOIN_ROOM"]
512
- }
513
- }
514
- ]
515
- ]
516
- };
517
-
518
- // src/actions/listRooms.ts
519
- var listRooms = {
520
- name: "MATRIX_LIST_ROOMS",
521
- similes: ["LIST_MATRIX_ROOMS", "SHOW_ROOMS", "GET_ROOMS", "MY_ROOMS"],
522
- description: "List all Matrix rooms the bot has joined",
523
- validate: async (_runtime, message, _state) => {
524
- return message.content.source === "matrix";
525
- },
526
- handler: async (runtime, message, _state, _options, callback) => {
527
- const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
528
- if (!matrixService || !matrixService.isConnected()) {
529
- if (callback) {
530
- await callback({ text: "Matrix service is not available.", source: "matrix" });
531
- }
532
- return { success: false, error: "Matrix service not available" };
533
- }
534
- const rooms = await matrixService.getJoinedRooms();
535
- const roomList = rooms.map((room) => {
536
- const name = room.name || room.canonicalAlias || room.roomId;
537
- const members = `${room.memberCount} members`;
538
- const encrypted = room.isEncrypted ? " (encrypted)" : "";
539
- return `- ${name} (${members})${encrypted}`;
540
- });
541
- const responseText = rooms.length > 0 ? `Joined ${rooms.length} room(s):
542
-
543
- ${roomList.join(`
544
- `)}` : "Not currently in any rooms.";
545
- if (callback) {
546
- await callback({
547
- text: responseText,
548
- source: message.content.source
549
- });
550
- }
551
- return {
552
- success: true,
553
- data: {
554
- roomCount: rooms.length,
555
- rooms: rooms.map((r) => ({
556
- roomId: r.roomId,
557
- name: r.name,
558
- alias: r.canonicalAlias,
559
- memberCount: r.memberCount,
560
- isEncrypted: r.isEncrypted
561
- }))
562
- }
563
- };
564
- },
565
- examples: [
566
- [
567
- {
568
- name: "{{user1}}",
569
- content: { text: "What rooms are you in?" }
570
- },
571
- {
572
- name: "{{agent}}",
573
- content: {
574
- text: "I'll list the rooms I've joined.",
575
- actions: ["MATRIX_LIST_ROOMS"]
576
- }
577
- }
578
- ]
579
- ]
580
- };
581
-
582
- // src/actions/sendMessage.ts
583
- import { composePromptFromState as composePromptFromState2, ModelType as ModelType2, parseJSONObjectFromText as parseJSONObjectFromText2 } from "@elizaos/core";
584
- var SEND_MESSAGE_TEMPLATE = `You are helping to extract send message parameters for Matrix.
585
-
586
- The user wants to send a message to a Matrix room.
587
-
588
- Recent conversation:
589
- {{recentMessages}}
590
-
591
- Extract the following:
592
- 1. text: The message text to send
593
- 2. roomId: The room ID (!room:server) or alias (#alias:server), or "current" for the current room
594
-
595
- Respond with a JSON object like:
596
- {
597
- "text": "The message to send",
598
- "roomId": "current"
794
+ throw new Error(`Matrix account '${normalized}' is not available in this service instance`);
795
+ }
599
796
  }
600
-
601
- Only respond with the JSON object, no other text.`;
602
- var sendMessage = {
603
- name: "MATRIX_SEND_MESSAGE",
604
- similes: ["SEND_MATRIX_MESSAGE", "MESSAGE_MATRIX", "MATRIX_TEXT"],
605
- description: "Send a message to a Matrix room",
606
- validate: async (_runtime, message, _state) => {
607
- return message.content.source === "matrix";
608
- },
609
- handler: async (runtime, message, state, _options, callback) => {
610
- const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
611
- if (!matrixService || !matrixService.isConnected()) {
612
- if (callback) {
613
- await callback({ text: "Matrix service is not available.", source: "matrix" });
614
- }
615
- return { success: false, error: "Matrix service not available" };
616
- }
617
- const composedState = state ?? {
618
- values: {},
619
- data: {},
620
- text: ""
621
- };
622
- const prompt = await composePromptFromState2({
623
- template: SEND_MESSAGE_TEMPLATE,
624
- state: composedState
625
- });
626
- let messageInfo = null;
627
- for (let attempt = 0;attempt < 3; attempt++) {
628
- const response = await runtime.useModel(ModelType2.TEXT_SMALL, {
629
- prompt
630
- });
631
- const parsed = parseJSONObjectFromText2(response);
632
- if (parsed?.text) {
633
- messageInfo = {
634
- text: String(parsed.text),
635
- roomId: String(parsed.roomId || "current")
636
- };
637
- break;
638
- }
639
- }
640
- if (!messageInfo || !messageInfo.text) {
641
- if (callback) {
642
- await callback({
643
- text: "I couldn't understand what message you want me to send. Please try again.",
644
- source: "matrix"
645
- });
646
- }
647
- return { success: false, error: "Could not extract message parameters" };
648
- }
649
- let targetRoomId;
650
- if (messageInfo.roomId && messageInfo.roomId !== "current") {
651
- if (isValidMatrixRoomId(messageInfo.roomId) || isValidMatrixRoomAlias(messageInfo.roomId)) {
652
- targetRoomId = messageInfo.roomId;
653
- }
654
- }
655
- const roomData = state?.data?.room;
656
- if (!targetRoomId && roomData?.roomId) {
657
- targetRoomId = roomData.roomId;
658
- }
659
- if (!targetRoomId) {
660
- if (callback) {
661
- await callback({
662
- text: "I couldn't determine which room to send to. Please specify a room.",
663
- source: "matrix"
664
- });
665
- }
666
- return { success: false, error: "Could not determine target room" };
667
- }
668
- const result = await matrixService.sendMessage(messageInfo.text, {
669
- roomId: targetRoomId
670
- });
671
- if (!result.success) {
672
- if (callback) {
673
- await callback({
674
- text: `Failed to send message: ${result.error}`,
675
- source: "matrix"
676
- });
677
- }
678
- return { success: false, error: result.error };
679
- }
680
- if (callback) {
681
- await callback({
682
- text: "Message sent successfully.",
683
- source: message.content.source
684
- });
685
- }
686
- return {
687
- success: true,
688
- data: {
689
- roomId: result.roomId,
690
- eventId: result.eventId
691
- }
692
- };
693
- },
694
- examples: [
695
- [
696
- {
697
- name: "{{user1}}",
698
- content: { text: "Send a message saying 'Hello everyone!'" }
699
- },
700
- {
701
- name: "{{agent}}",
702
- content: {
703
- text: "I'll send that message to the room.",
704
- actions: ["MATRIX_SEND_MESSAGE"]
705
- }
706
- }
707
- ]
708
- ]
709
- };
710
-
711
- // src/actions/sendReaction.ts
712
- import { composePromptFromState as composePromptFromState3, ModelType as ModelType3, parseJSONObjectFromText as parseJSONObjectFromText3 } from "@elizaos/core";
713
- var SEND_REACTION_TEMPLATE = `You are helping to extract reaction parameters for Matrix.
714
-
715
- The user wants to react to a Matrix message with an emoji.
716
-
717
- Recent conversation:
718
- {{recentMessages}}
719
-
720
- Extract the following:
721
- 1. emoji: The emoji to react with (single emoji character)
722
- 2. eventId: The event ID of the message to react to (starts with $)
723
-
724
- Respond with a JSON object like:
725
- {
726
- "emoji": "\uD83D\uDC4D",
727
- "eventId": "$event123"
797
+ // src/connector-account-provider.ts
798
+ var MATRIX_PROVIDER_ID = "matrix";
799
+ function accountKey(settings) {
800
+ if (settings.homeserver && settings.userId) {
801
+ return `${settings.homeserver}/${settings.userId}`;
802
+ }
803
+ return normalizeMatrixAccountId(settings.accountId);
728
804
  }
729
-
730
- Only respond with the JSON object, no other text.`;
731
- var sendReaction = {
732
- name: "MATRIX_SEND_REACTION",
733
- similes: ["REACT_MATRIX", "MATRIX_REACT", "ADD_MATRIX_REACTION"],
734
- description: "React to a Matrix message with an emoji",
735
- validate: async (_runtime, message, _state) => {
736
- return message.content.source === "matrix";
737
- },
738
- handler: async (runtime, message, state, _options, callback) => {
739
- const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
740
- if (!matrixService || !matrixService.isConnected()) {
741
- if (callback) {
742
- await callback({ text: "Matrix service is not available.", source: "matrix" });
743
- }
744
- return { success: false, error: "Matrix service not available" };
745
- }
746
- const composedState = state ?? {
747
- values: {},
748
- data: {},
749
- text: ""
750
- };
751
- const prompt = await composePromptFromState3({
752
- template: SEND_REACTION_TEMPLATE,
753
- state: composedState
754
- });
755
- let reactionInfo = null;
756
- for (let attempt = 0;attempt < 3; attempt++) {
757
- const response = await runtime.useModel(ModelType3.TEXT_SMALL, {
758
- prompt
759
- });
760
- const parsed = parseJSONObjectFromText3(response);
761
- if (parsed?.emoji && parsed?.eventId) {
762
- reactionInfo = {
763
- emoji: String(parsed.emoji),
764
- eventId: String(parsed.eventId)
765
- };
766
- break;
767
- }
768
- }
769
- if (!reactionInfo) {
770
- if (callback) {
771
- await callback({
772
- text: "I couldn't understand the reaction request. Please specify the emoji and message.",
773
- source: "matrix"
774
- });
775
- }
776
- return { success: false, error: "Could not extract reaction parameters" };
777
- }
778
- const roomData = state?.data?.room;
779
- const roomId = roomData?.roomId;
780
- if (!roomId) {
781
- if (callback) {
782
- await callback({
783
- text: "I couldn't determine which room this is in.",
784
- source: "matrix"
785
- });
786
- }
787
- return { success: false, error: "Could not determine room" };
788
- }
789
- const result = await matrixService.sendReaction(roomId, reactionInfo.eventId, reactionInfo.emoji);
790
- if (!result.success) {
791
- if (callback) {
792
- await callback({
793
- text: `Failed to add reaction: ${result.error}`,
794
- source: "matrix"
795
- });
796
- }
797
- return { success: false, error: result.error };
798
- }
799
- if (callback) {
800
- await callback({
801
- text: `Added ${reactionInfo.emoji} reaction.`,
802
- source: message.content.source
803
- });
804
- }
805
- return {
806
- success: true,
807
- data: {
808
- emoji: reactionInfo.emoji,
809
- eventId: reactionInfo.eventId,
810
- roomId
811
- }
812
- };
813
- },
814
- examples: [
815
- [
816
- {
817
- name: "{{user1}}",
818
- content: { text: "React to the last message with a thumbs up" }
819
- },
820
- {
821
- name: "{{agent}}",
822
- content: {
823
- text: "I'll add a thumbs up reaction.",
824
- actions: ["MATRIX_SEND_REACTION"]
825
- }
826
- }
827
- ]
828
- ]
829
- };
830
-
831
- // src/providers/roomState.ts
832
- var roomStateProvider = {
833
- name: "matrixRoomState",
834
- description: "Provides information about the current Matrix room context",
835
- dynamic: true,
836
- get: async (runtime, message, state) => {
837
- if (message.content.source !== "matrix") {
838
- return {
839
- data: {},
840
- values: {},
841
- text: ""
842
- };
843
- }
844
- const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
845
- if (!matrixService || !matrixService.isConnected()) {
805
+ function toConnectorAccount(settings) {
806
+ const now = Date.now();
807
+ const configured = Boolean(settings.homeserver && settings.userId && settings.accessToken);
808
+ return {
809
+ id: normalizeMatrixAccountId(settings.accountId),
810
+ provider: MATRIX_PROVIDER_ID,
811
+ label: settings.userId || settings.accountId,
812
+ role: "OWNER",
813
+ purpose: ["messaging"],
814
+ accessGate: "open",
815
+ status: settings.enabled !== false && configured ? "connected" : "disabled",
816
+ externalId: accountKey(settings),
817
+ displayHandle: settings.userId || undefined,
818
+ createdAt: now,
819
+ updatedAt: now,
820
+ metadata: {
821
+ homeserver: settings.homeserver ?? "",
822
+ userId: settings.userId ?? "",
823
+ deviceId: settings.deviceId ?? "",
824
+ encryption: settings.encryption ?? false,
825
+ autoJoin: settings.autoJoin ?? false
826
+ }
827
+ };
828
+ }
829
+ function createMatrixConnectorAccountProvider(runtime) {
830
+ return {
831
+ provider: MATRIX_PROVIDER_ID,
832
+ label: "Matrix",
833
+ listAccounts: async (_manager) => {
834
+ const ids = listMatrixAccountIds(runtime);
835
+ if (ids.length === 0) {
836
+ return [
837
+ toConnectorAccount(resolveMatrixAccountSettings(runtime, DEFAULT_MATRIX_ACCOUNT_ID))
838
+ ];
839
+ }
840
+ return ids.map((id) => toConnectorAccount(resolveMatrixAccountSettings(runtime, id)));
841
+ },
842
+ createAccount: async (input, _manager) => {
846
843
  return {
847
- data: { connected: false },
848
- values: { connected: false },
849
- text: ""
844
+ ...input,
845
+ provider: MATRIX_PROVIDER_ID,
846
+ role: input.role ?? "OWNER",
847
+ purpose: input.purpose ?? ["messaging"],
848
+ accessGate: input.accessGate ?? "open",
849
+ status: input.status ?? "pending"
850
850
  };
851
- }
852
- const agentName = state?.agentName || "The agent";
853
- const room = state?.data?.room;
854
- const roomId = room?.roomId;
855
- const roomName = room?.name;
856
- const isEncrypted = room?.isEncrypted;
857
- const isDirect = room?.isDirect;
858
- const memberCount = room?.memberCount;
859
- const userId = matrixService.getUserId();
860
- const displayName = getMatrixLocalpart(userId);
861
- let responseText = "";
862
- if (isDirect) {
863
- responseText = `${agentName} is in a direct message conversation on Matrix.`;
864
- } else {
865
- const roomLabel = roomName || roomId || "a Matrix room";
866
- responseText = `${agentName} is currently in Matrix room "${roomLabel}".`;
867
- if (memberCount) {
868
- responseText += ` The room has ${memberCount} members.`;
869
- }
870
- }
871
- if (isEncrypted) {
872
- responseText += " This room has end-to-end encryption enabled.";
873
- }
874
- responseText += `
851
+ },
852
+ patchAccount: async (_accountId, patch, _manager) => {
853
+ return { ...patch, provider: MATRIX_PROVIDER_ID };
854
+ },
855
+ deleteAccount: async (_accountId, _manager) => {}
856
+ };
857
+ }
858
+
859
+ // src/workflow-credential-provider.ts
860
+ import { Service as Service2 } from "@elizaos/core";
861
+ var WORKFLOW_CREDENTIAL_PROVIDER_TYPE = "workflow_credential_provider";
862
+ var SUPPORTED = ["matrixApi"];
875
863
 
876
- Matrix is a decentralized communication protocol. ${agentName} is logged in as ${userId}.`;
864
+ class MatrixWorkflowCredentialProvider extends Service2 {
865
+ static serviceType = WORKFLOW_CREDENTIAL_PROVIDER_TYPE;
866
+ capabilityDescription = "Supplies Matrix credentials to the workflow plugin.";
867
+ static async start(runtime) {
868
+ return new MatrixWorkflowCredentialProvider(runtime);
869
+ }
870
+ async stop() {}
871
+ async resolve(_userId, credType) {
872
+ if (credType !== "matrixApi")
873
+ return null;
874
+ const accessToken = this.runtime.getSetting("MATRIX_ACCESS_TOKEN");
875
+ const homeserver = this.runtime.getSetting("MATRIX_HOMESERVER");
876
+ if (!accessToken?.trim() || !homeserver?.trim())
877
+ return null;
877
878
  return {
878
- data: {
879
- roomId,
880
- roomName,
881
- isEncrypted: isEncrypted || false,
882
- isDirect: isDirect || false,
883
- memberCount: memberCount || 0,
884
- userId,
885
- displayName,
886
- homeserver: matrixService.getHomeserver(),
887
- connected: true
888
- },
889
- values: {
890
- roomId,
891
- roomName,
892
- isEncrypted: isEncrypted || false,
893
- isDirect: isDirect || false,
894
- memberCount: memberCount || 0,
895
- userId
896
- },
897
- text: responseText
879
+ status: "credential_data",
880
+ data: { accessToken: accessToken.trim(), homeserverUrl: homeserver.trim() }
898
881
  };
899
882
  }
900
- };
901
-
902
- // src/providers/userContext.ts
903
- var userContextProvider = {
904
- name: "matrixUserContext",
905
- description: "Provides information about the Matrix user in the current conversation",
906
- dynamic: true,
907
- get: async (runtime, message, state) => {
908
- if (message.content.source !== "matrix") {
909
- return {
910
- data: {},
911
- values: {},
912
- text: ""
913
- };
914
- }
915
- const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
916
- if (!matrixService || !matrixService.isConnected()) {
917
- return {
918
- data: {},
919
- values: {},
920
- text: ""
921
- };
922
- }
923
- const agentName = state?.agentName || "The agent";
924
- const metadata = message.content.metadata;
925
- const senderInfo = metadata?.senderInfo;
926
- if (!senderInfo) {
927
- return {
928
- data: {},
929
- values: {},
930
- text: ""
931
- };
932
- }
933
- const displayName = getMatrixUserDisplayName(senderInfo);
934
- const localpart = getMatrixLocalpart(senderInfo.userId);
935
- const responseText = `${agentName} is talking to ${displayName} (${senderInfo.userId}) on Matrix.`;
883
+ checkCredentialTypes(credTypes) {
936
884
  return {
937
- data: {
938
- userId: senderInfo.userId,
939
- displayName,
940
- localpart,
941
- avatarUrl: senderInfo.avatarUrl
942
- },
943
- values: {
944
- userId: senderInfo.userId,
945
- displayName,
946
- localpart
947
- },
948
- text: responseText
885
+ supported: credTypes.filter((t) => SUPPORTED.includes(t)),
886
+ unsupported: credTypes.filter((t) => !SUPPORTED.includes(t))
949
887
  };
950
888
  }
951
- };
889
+ }
952
890
 
953
891
  // src/index.ts
954
892
  var matrixPlugin = {
955
893
  name: "matrix",
956
894
  description: "Matrix messaging integration plugin for ElizaOS with E2EE support",
957
- services: [MatrixService],
958
- actions: [sendMessage, sendReaction, listRooms, joinRoom],
959
- providers: [roomStateProvider, userContextProvider],
895
+ services: [MatrixService, MatrixWorkflowCredentialProvider],
896
+ actions: [],
897
+ providers: [],
960
898
  tests: [],
899
+ autoEnable: {
900
+ connectorKeys: ["matrix"]
901
+ },
961
902
  init: async (_config, runtime) => {
903
+ try {
904
+ const manager = getConnectorAccountManager(runtime);
905
+ manager.registerProvider(createMatrixConnectorAccountProvider(runtime));
906
+ } catch (err) {
907
+ logger2.warn({
908
+ src: "plugin:matrix",
909
+ err: err instanceof Error ? err.message : String(err)
910
+ }, "Failed to register Matrix provider with ConnectorAccountManager");
911
+ }
962
912
  const homeserver = runtime.getSetting("MATRIX_HOMESERVER");
963
913
  const userId = runtime.getSetting("MATRIX_USER_ID");
964
914
  const accessToken = runtime.getSetting("MATRIX_ACCESS_TOKEN");
@@ -1003,13 +953,12 @@ var matrixPlugin = {
1003
953
  };
1004
954
  var src_default = matrixPlugin;
1005
955
  export {
1006
- userContextProvider,
1007
- sendReaction,
1008
- sendMessage,
1009
- roomStateProvider,
956
+ resolveMatrixAccountSettings,
957
+ resolveDefaultMatrixAccountId,
958
+ readMatrixAccountId,
959
+ normalizeMatrixAccountId,
1010
960
  matrixMxcToHttp,
1011
- listRooms,
1012
- joinRoom,
961
+ listMatrixAccountIds,
1013
962
  isValidMatrixUserId,
1014
963
  isValidMatrixRoomId,
1015
964
  isValidMatrixRoomAlias,
@@ -1025,7 +974,8 @@ export {
1025
974
  MatrixConfigurationError,
1026
975
  MatrixApiError,
1027
976
  MAX_MATRIX_MESSAGE_LENGTH,
1028
- MATRIX_SERVICE_NAME
977
+ MATRIX_SERVICE_NAME,
978
+ DEFAULT_MATRIX_ACCOUNT_ID
1029
979
  };
1030
980
 
1031
- //# debugId=5529CFEE8ACF852C64756E2164756E21
981
+ //# debugId=2890053EB234064664756E2164756E21