khala-cli 0.4.8 → 0.4.10

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 (53) hide show
  1. package/README.md +4 -4
  2. package/dist/THIRD_PARTY_NOTICES.txt +809 -0
  3. package/dist/chunk-2AMWQ7RB.mjs +1127 -0
  4. package/dist/chunk-3E5DH3Y6.mjs +9864 -0
  5. package/dist/{chunk-IQJQ4RF5.mjs → chunk-4BP7V3OP.mjs} +12 -1
  6. package/dist/{chunk-FV32AQTC.mjs → chunk-4BZQ4GVW.mjs} +10 -6
  7. package/dist/chunk-5JSGA6OB.mjs +1483 -0
  8. package/dist/chunk-6AQVW5W7.mjs +60 -0
  9. package/dist/{chunk-W6R4UJUQ.mjs → chunk-6J2PUM7L.mjs} +16 -9
  10. package/dist/chunk-6SUV3EW6.mjs +27 -0
  11. package/dist/{chunk-RNDUFTBF.mjs → chunk-ARIDZOTI.mjs} +499 -115
  12. package/dist/chunk-C2WEDMAF.mjs +15 -0
  13. package/dist/{chunk-Z4JF2M5C.mjs → chunk-CHC4VZF5.mjs} +9 -9
  14. package/dist/chunk-CKLCR5G4.mjs +1369 -0
  15. package/dist/chunk-EVT2DWNZ.mjs +58 -0
  16. package/dist/chunk-FJVVSKZ3.mjs +22 -0
  17. package/dist/{chunk-APMWDF6B.mjs → chunk-G3NMB4PY.mjs} +4 -2
  18. package/dist/chunk-HHEZVPRR.mjs +7 -0
  19. package/dist/{chunk-54XFVSEC.mjs → chunk-JKOJQTFT.mjs} +103 -5
  20. package/dist/{chunk-ZJ4W7P33.mjs → chunk-NDMFD5IM.mjs} +15 -10
  21. package/dist/{chunk-VKQ3KUYO.mjs → chunk-OEV5DFLW.mjs} +77 -7
  22. package/dist/chunk-OIZ4R5H7.mjs +173 -0
  23. package/dist/{chunk-CJTCU755.mjs → chunk-PJKI7SRI.mjs} +256 -26
  24. package/dist/chunk-PN25BUQ4.mjs +599 -0
  25. package/dist/chunk-QDPJ4VT2.mjs +432 -0
  26. package/dist/{chunk-2DHIAFEV.mjs → chunk-SRZTHCXP.mjs} +24 -10
  27. package/dist/chunk-TSK3HJEG.mjs +20 -0
  28. package/dist/chunk-UX34IVN5.mjs +33 -0
  29. package/dist/{chunk-BKLW2SPG.mjs → chunk-VAC3JTAV.mjs} +34 -17
  30. package/dist/{chunk-VAI7IUI4.mjs → chunk-VDZK4LW3.mjs} +30 -12
  31. package/dist/chunk-W6HM6F2U.mjs +901 -0
  32. package/dist/{chunk-H3K4GA2P.mjs → chunk-WPNMWNCL.mjs} +33 -21
  33. package/dist/chunk-WUDXHF2Q.mjs +14 -0
  34. package/dist/chunk-WZJ6DQJV.mjs +19 -0
  35. package/dist/{chunk-5LIE5GUU.mjs → chunk-XR625T3I.mjs} +5 -1
  36. package/dist/chunk-ZSKLNZWZ.mjs +224 -0
  37. package/dist/iterm2_send.py +56 -0
  38. package/dist/khala.mjs +20 -13
  39. package/dist/web/assets/local-CSPuGnH2.css +1 -0
  40. package/dist/web/assets/local-Dtyxaacx.js +9 -0
  41. package/dist/web/index.html +2 -2
  42. package/package.json +5 -3
  43. package/dist/chunk-2VCAW4GH.mjs +0 -34
  44. package/dist/chunk-5BQTYU66.mjs +0 -886
  45. package/dist/chunk-5GLTKWJ5.mjs +0 -1
  46. package/dist/chunk-5PUQT3YS.mjs +0 -569
  47. package/dist/chunk-7HBRFY3Z.mjs +0 -8
  48. package/dist/chunk-CBPLCCRZ.mjs +0 -191
  49. package/dist/chunk-FH34B3I3.mjs +0 -235
  50. package/dist/chunk-NKX3SLS7.mjs +0 -227
  51. package/dist/chunk-VUSV2ADL.mjs +0 -267
  52. package/dist/web/assets/local-BQl2KrE1.js +0 -9
  53. package/dist/web/assets/local-OwDq47O4.css +0 -1
@@ -1,20 +1,32 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
- import "./chunk-5GLTKWJ5.mjs";
3
2
  import {
4
- CHANNEL_EVENT_TYPE,
5
- encodeChannelEvent,
6
- memberRenameContent
7
- } from "./chunk-54XFVSEC.mjs";
3
+ retryUnexpired
4
+ } from "./chunk-C2WEDMAF.mjs";
5
+ import {
6
+ RESTORE_HISTORY_MAX_PAGES
7
+ } from "./chunk-WUDXHF2Q.mjs";
8
+ import "./chunk-HHEZVPRR.mjs";
8
9
  import {
9
10
  LISTENING_MODE_COMMAND_TYPE,
10
11
  LISTENING_MODE_MEMBER_KEY,
11
12
  memberListeningMode
12
- } from "./chunk-IQJQ4RF5.mjs";
13
- import "./chunk-NKX3SLS7.mjs";
14
- import "./chunk-7HBRFY3Z.mjs";
13
+ } from "./chunk-4BP7V3OP.mjs";
14
+ import {
15
+ CHANNEL_EVENT_TYPE,
16
+ encodeChannelEvent,
17
+ memberRenameContent
18
+ } from "./chunk-JKOJQTFT.mjs";
19
+ import {
20
+ StateError
21
+ } from "./chunk-PN25BUQ4.mjs";
22
+ import {
23
+ init_define_KHALA_BUNDLE
24
+ } from "./chunk-EVT2DWNZ.mjs";
15
25
 
16
26
  // src/matrix/session.ts
17
- import { createClient, ClientEvent, RoomEvent, MatrixEventEvent, SyncState, Direction, Method, EventType } from "matrix-js-sdk";
27
+ init_define_KHALA_BUNDLE();
28
+ import { randomUUID } from "node:crypto";
29
+ import { createClient, ClientEvent, RoomEvent, MatrixEventEvent, SyncState, SetPresence, Direction, Method, EventType } from "matrix-js-sdk";
18
30
  function message(event) {
19
31
  const type = event.getType();
20
32
  const eventId = event.getId();
@@ -36,20 +48,48 @@ function message(event) {
36
48
  }
37
49
  async function createAgentMatrixSession(creds, opts) {
38
50
  const started = Date.now();
51
+ const rejoinTxnId = `khala.rejoin.${randomUUID()}`;
39
52
  const log = (line) => opts?.log?.(line);
40
- const client = createClient({ baseUrl: creds.homeserver, userId: creds.userId, accessToken: creds.accessToken, deviceId: creds.deviceId });
53
+ const persistent = opts?.cryptoStore ? await (await import("./chunk-3E5DH3Y6.mjs")).openCryptoStore(opts.cryptoStore.dir, opts.cryptoStore.root, creds) : void 0;
54
+ const client = createClient({ ...persistent ? { store: persistent.sync } : {}, baseUrl: creds.homeserver, userId: creds.userId, accessToken: creds.accessToken, deviceId: creds.deviceId });
41
55
  const handlers = /* @__PURE__ */ new Set();
42
56
  const ended = /* @__PURE__ */ new Set();
43
57
  const modeHandlers = /* @__PURE__ */ new Set();
44
58
  const inviters = /* @__PURE__ */ new Map();
45
59
  const emitted = /* @__PURE__ */ new Set();
46
60
  const liveEvents = /* @__PURE__ */ new Set();
61
+ const undecryptableIds = new Map((persistent?.undecryptableEventIds ?? []).map((entry) => [entry.id, entry]));
62
+ let retrySave = Promise.resolve();
63
+ const saveRetries = () => {
64
+ const entries = [...undecryptableIds.values()];
65
+ retrySave = retrySave.then(() => persistent?.rememberUndecryptable(entries)).catch(() => log("decryption_retry_save_failed"));
66
+ };
67
+ const rememberDecryption = (event) => {
68
+ const id = event.getId();
69
+ if (stopped || !persistent || !id) return;
70
+ const failed = event.getType() === "m.room.encrypted" || event.isDecryptionFailure();
71
+ if (failed) {
72
+ if (undecryptableIds.has(id)) return;
73
+ undecryptableIds.set(id, { id, firstSeen: Date.now() });
74
+ } else if (!undecryptableIds.delete(id)) return;
75
+ if (undecryptableIds.size > 100) undecryptableIds.delete(undecryptableIds.keys().next().value);
76
+ saveRetries();
77
+ };
47
78
  const joinTimes = /* @__PURE__ */ new Map();
48
79
  const cancellations = /* @__PURE__ */ new Set();
49
80
  let joinedRoom;
50
81
  let stopped = false;
82
+ let presenceTimer;
83
+ let away;
84
+ const publishPresence = () => {
85
+ void client.setSyncPresence(away ? SetPresence.Offline : void 0);
86
+ void client.setPresence(away ? { presence: "offline", status_msg: away } : { presence: "online", status_msg: "" }).catch(() => log("presence_failed"));
87
+ };
88
+ let recovering = persistent?.restored ?? false;
51
89
  const deliver = (event) => {
52
- if (stopped || !liveEvents.has(event)) return;
90
+ if (stopped) return;
91
+ if (liveEvents.has(event)) rememberDecryption(event);
92
+ if (recovering || !liveEvents.has(event)) return;
53
93
  const command = event.getType() === LISTENING_MODE_COMMAND_TYPE && !event.isDecryptionFailure() && event.getId() && event.getRoomId() && event.getSender() ? { eventId: event.getId(), roomId: event.getRoomId(), sender: event.getSender(), ts: event.getTs(), content: event.getContent() } : void 0;
54
94
  const m = command ?? message(event);
55
95
  if (!m) {
@@ -85,9 +125,12 @@ async function createAgentMatrixSession(creds, opts) {
85
125
  }
86
126
  };
87
127
  const timeline = (event, _room, toStart, _removed, data) => {
88
- if (stopped || toStart || data?.liveEvent === false) return;
128
+ if (stopped || toStart || data?.liveEvent === false && !persistent?.restored) return;
89
129
  liveEvents.add(event);
90
- void client.decryptEventIfNeeded(event).then(() => deliver(event), () => log("live_decryption_failed"));
130
+ void client.decryptEventIfNeeded(event).then(() => deliver(event), () => {
131
+ rememberDecryption(event);
132
+ log("live_decryption_failed");
133
+ });
91
134
  };
92
135
  const decrypted = (event) => deliver(event);
93
136
  const syncLog = (state, _previous, data) => {
@@ -152,9 +195,53 @@ async function createAgentMatrixSession(creds, opts) {
152
195
  check();
153
196
  });
154
197
  };
155
- const stop = async () => {
198
+ let stopping;
199
+ let wipe = false;
200
+ const stop = () => stopping ??= stopSession();
201
+ const stopSession = async () => {
156
202
  if (stopped) return;
157
203
  stopped = true;
204
+ clearInterval(presenceTimer);
205
+ if (presenceTimer && !wipe) {
206
+ await Promise.race([
207
+ client.setPresence({ presence: "offline", status_msg: "session ended" }).catch(() => log("presence_failed")),
208
+ new Promise((resolve) => setTimeout(resolve, 3e3).unref())
209
+ ]);
210
+ }
211
+ await retrySave;
212
+ if (wipe) {
213
+ try {
214
+ await client.http.authedRequest(Method.Post, "/logout", void 0, {}, { localTimeoutMs: 5e3 });
215
+ } catch (error) {
216
+ if (matrixCode(error) !== "M_UNKNOWN_TOKEN" && !(typeof error === "object" && error !== null && "httpStatus" in error && error.httpStatus === 401)) {
217
+ log("discarded_device_logout_failed");
218
+ console.error("khala: discarded_device_logout_failed");
219
+ }
220
+ }
221
+ }
222
+ if (wipe && persistent?.forgetIdentity) {
223
+ try {
224
+ await persistent.forgetIdentity();
225
+ } catch {
226
+ log("crypto_cleanup_failed");
227
+ }
228
+ }
229
+ let syncStopped;
230
+ if (persistent && client.getSyncState() !== null && client.getSyncState() !== SyncState.Stopped) {
231
+ syncStopped = new Promise((resolve, reject) => {
232
+ const done = (state) => {
233
+ if (state === SyncState.Stopped) finish();
234
+ };
235
+ const timer = setTimeout(() => finish(new Error("crypto_sync_stop_timeout")), 1e4);
236
+ const finish = (error) => {
237
+ clearTimeout(timer);
238
+ client.removeListener(ClientEvent.Sync, done);
239
+ if (error) reject(error);
240
+ else resolve();
241
+ };
242
+ client.on(ClientEvent.Sync, done);
243
+ });
244
+ }
158
245
  client.stopClient();
159
246
  client.removeListener(RoomEvent.Timeline, timeline);
160
247
  client.removeListener(MatrixEventEvent.Decrypted, decrypted);
@@ -166,6 +253,14 @@ async function createAgentMatrixSession(creds, opts) {
166
253
  handlers.clear();
167
254
  modeHandlers.clear();
168
255
  ended.clear();
256
+ if (persistent) {
257
+ try {
258
+ await syncStopped;
259
+ if (wipe) await persistent.wipe();
260
+ } finally {
261
+ await persistent.close();
262
+ }
263
+ }
169
264
  };
170
265
  const membershipEnded = (room, membership) => {
171
266
  if (stopped || room.roomId !== joinedRoom || !["leave", "ban"].includes(membership)) return;
@@ -174,7 +269,11 @@ async function createAgentMatrixSession(creds, opts) {
174
269
  const end = (reason) => {
175
270
  if (stopped) return;
176
271
  const listeners = [...ended];
177
- void stop();
272
+ wipe = true;
273
+ void stop().catch(() => {
274
+ log("crypto_cleanup_failed");
275
+ console.error("khala: crypto_cleanup_failed");
276
+ });
178
277
  for (const handler of listeners) {
179
278
  try {
180
279
  handler(reason);
@@ -184,7 +283,13 @@ async function createAgentMatrixSession(creds, opts) {
184
283
  }
185
284
  };
186
285
  try {
187
- await client.initRustCrypto({ useIndexedDB: false });
286
+ if (persistent) await persistent.sync.startup();
287
+ try {
288
+ await client.initRustCrypto(persistent ? { useIndexedDB: true, cryptoDatabasePrefix: persistent.prefix } : { useIndexedDB: false });
289
+ } catch (error) {
290
+ if (!persistent?.restored || error instanceof StateError && error.code === "unsafe_state_dir") throw error;
291
+ throw new (await import("./chunk-3E5DH3Y6.mjs")).CryptoStoreCorruptError();
292
+ }
188
293
  const crypto = client.getCrypto();
189
294
  if (!crypto) throw new Error("crypto_unavailable");
190
295
  log(`crypto_version=${crypto.getVersion()}`);
@@ -210,6 +315,9 @@ async function createAgentMatrixSession(creds, opts) {
210
315
  }, () => client.getSyncState() === SyncState.Prepared || client.getSyncState() === SyncState.Syncing, 3e4, "sync_timeout");
211
316
  await Promise.all([client.startClient({ initialSyncLimit: 30 }), prepared]);
212
317
  log(`session_ready_ms=${Date.now() - started}`);
318
+ publishPresence();
319
+ presenceTimer = setInterval(publishPresence, 6e4);
320
+ presenceTimer.unref();
213
321
  } catch (error) {
214
322
  await stop();
215
323
  throw error;
@@ -227,6 +335,8 @@ async function createAgentMatrixSession(creds, opts) {
227
335
  }, ready, timeoutMs, error);
228
336
  return {
229
337
  userId: creds.userId,
338
+ ...persistent?.recovered ? { cryptoReset: true } : {},
339
+ ...persistent?.restored ? { recoversOnJoin: true } : {},
230
340
  onEnded(handler) {
231
341
  if (!stopped) ended.add(handler);
232
342
  return () => {
@@ -262,7 +372,6 @@ async function createAgentMatrixSession(creds, opts) {
262
372
  async join(roomId) {
263
373
  if (stopped) throw new Error("session_stopped");
264
374
  if (joinedRoom === roomId) return;
265
- const rejoinedAt = Date.now();
266
375
  const membership = client.getRoom(roomId)?.getMyMembership();
267
376
  if (membership !== "join") {
268
377
  if (membership !== "invite") throw new Error("not_invited");
@@ -277,20 +386,96 @@ async function createAgentMatrixSession(creds, opts) {
277
386
  const owner = ownJoin.getContent()["com.khala.invited_by"];
278
387
  if (typeof owner === "string") inviters.set(roomId, owner);
279
388
  const event = encodeChannelEvent({ kind: "member", summary: `${ownJoin.getContent().displayname ?? creds.userId} rejoined`, status: "info", source: { system: "khala-agent" } });
280
- if (event.ok) await client.sendEvent(roomId, CHANNEL_EVENT_TYPE, event.value, `khala.rejoin.${creds.deviceId}`);
389
+ if (event.ok) await client.sendEvent(roomId, CHANNEL_EVENT_TYPE, event.value, rejoinTxnId);
281
390
  }
282
391
  if (membership !== "join" && inviters.has(roomId)) {
283
392
  const content = { ...ownJoin.getContent(), membership: "join", "com.khala.invited_by": inviters.get(roomId) };
284
393
  await client.sendStateEvent(roomId, EventType.RoomMember, content, creds.userId);
285
394
  }
286
- joinTimes.set(roomId, membership === "join" ? rejoinedAt : ownJoin.getTs());
395
+ const joinedAt = persistent?.joinedAt ?? ownJoin.getTs();
396
+ await persistent?.rememberJoin(joinedAt);
397
+ joinTimes.set(roomId, joinedAt);
398
+ if (persistent?.restored) {
399
+ let pruned = false;
400
+ for (const [id, entry] of [...undecryptableIds]) {
401
+ if (stopped) throw new Error("session_stopped");
402
+ if (!retryUnexpired(entry)) {
403
+ undecryptableIds.delete(id);
404
+ pruned = true;
405
+ continue;
406
+ }
407
+ try {
408
+ const raw = await client.fetchRoomEvent(roomId, id);
409
+ const event = client.getEventMapper()({ ...raw, room_id: roomId });
410
+ if (event.getTs() < joinedAt) {
411
+ undecryptableIds.delete(id);
412
+ pruned = true;
413
+ continue;
414
+ }
415
+ liveEvents.add(event);
416
+ await client.decryptEventIfNeeded(event).catch(() => log("recovery_decryption_failed"));
417
+ rememberDecryption(event);
418
+ } catch (error) {
419
+ const status = typeof error === "object" && error !== null && "httpStatus" in error ? error.httpStatus : void 0;
420
+ if (matrixCode(error) === "M_NOT_FOUND" || matrixCode(error) === "M_FORBIDDEN" || status === 404 || status === 403) {
421
+ undecryptableIds.delete(id);
422
+ pruned = true;
423
+ }
424
+ log("recovery_event_retry_failed");
425
+ }
426
+ }
427
+ if (pruned) saveRetries();
428
+ let token = null;
429
+ const seenTokens = /* @__PURE__ */ new Set();
430
+ let pages = 0;
431
+ let undecryptable = 0;
432
+ for (; ; ) {
433
+ if (stopped) throw new Error("session_stopped");
434
+ const page = await client.createMessagesRequest(roomId, token, 100, Direction.Backward);
435
+ pages++;
436
+ let reachedBoundary = false;
437
+ for (const raw of page.chunk) {
438
+ const event = client.getEventMapper()({ ...raw, room_id: roomId });
439
+ if (opts?.restoreStopAtEventId && event.getId() === opts.restoreStopAtEventId || event.getTs() < joinedAt) {
440
+ reachedBoundary = true;
441
+ break;
442
+ }
443
+ liveEvents.add(event);
444
+ await client.decryptEventIfNeeded(event).catch(() => log("recovery_decryption_failed"));
445
+ rememberDecryption(event);
446
+ if (event.getType() === "m.room.encrypted" || event.isDecryptionFailure()) {
447
+ undecryptable++;
448
+ liveEvents.delete(event);
449
+ }
450
+ }
451
+ if (reachedBoundary || page.chunk.length === 0 || !page.end) break;
452
+ if (pages >= RESTORE_HISTORY_MAX_PAGES) {
453
+ const count = `restore_catchup_truncated pages=${pages}`;
454
+ if (opts?.log) log(count);
455
+ else process.stderr.write(`${count}
456
+ `);
457
+ break;
458
+ }
459
+ if (seenTokens.has(page.end)) throw new Error("recovery_pagination_stalled");
460
+ seenTokens.add(page.end);
461
+ token = page.end;
462
+ }
463
+ if (undecryptable > 0) {
464
+ const count = `history_undecryptable=${undecryptable}`;
465
+ if (opts?.log) log(count);
466
+ else process.stderr.write(`${count}
467
+ `);
468
+ }
469
+ }
470
+ recovering = false;
287
471
  if (!joinedRoom) client.on(RoomEvent.MyMembership, membershipEnded);
288
472
  joinedRoom = roomId;
289
- for (const event of liveEvents) {
473
+ for (const event of [...liveEvents].sort((a, b) => a.getTs() - b.getTs())) {
290
474
  if (event.getRoomId() === roomId) deliver(event);
291
475
  }
292
476
  },
293
- async history(roomId, limit, before) {
477
+ joinedAt: (roomId) => joinTimes.get(roomId),
478
+ async history(roomId, limit, before, options) {
294
479
  if (stopped) throw new Error("session_stopped");
295
480
  let token = null;
296
481
  if (before !== void 0) {
@@ -300,17 +485,27 @@ async function createAgentMatrixSession(creds, opts) {
300
485
  const res = await guarded(roomId, () => client.createMessagesRequest(roomId, token, limit, Direction.Backward));
301
486
  const messages = [];
302
487
  let undecryptable = 0;
488
+ let oldest;
489
+ let oldestTs;
490
+ let reachedBoundary = false;
303
491
  for (const raw of res.chunk) {
304
492
  const event = client.getEventMapper()({ ...raw, room_id: roomId });
493
+ oldest = event.getId();
494
+ oldestTs = Math.min(oldestTs ?? event.getTs(), event.getTs());
495
+ if (options?.stopAtEventId && oldest === options.stopAtEventId) {
496
+ reachedBoundary = true;
497
+ break;
498
+ }
305
499
  try {
306
500
  await client.decryptEventIfNeeded(event);
307
501
  } catch {
308
502
  }
503
+ rememberDecryption(event);
309
504
  if (event.getType() === "m.room.encrypted" || event.isDecryptionFailure()) {
310
505
  undecryptable++;
311
506
  const eventId = event.getId(), sender = event.getSender();
312
- if (eventId && sender && eventId !== before) {
313
- const body = "[Encrypted message unavailable: this device does not have its key. Messages from before joining may not have been shared.]";
507
+ if (options?.includeUnavailable !== false && eventId && sender && eventId !== before) {
508
+ const body = "[Encrypted message unavailable: this device does not have its key. Keys may not have been shared for messages sent before joining or while this agent was offline.]";
314
509
  messages.push({
315
510
  eventId,
316
511
  roomId,
@@ -326,10 +521,16 @@ async function createAgentMatrixSession(creds, opts) {
326
521
  const m = message(event);
327
522
  if (m && m.eventId !== before) messages.push(m);
328
523
  }
329
- log(`history_undecryptable=${undecryptable}`);
524
+ const count = `history_undecryptable=${undecryptable}`;
525
+ log(count);
526
+ if (options?.includeUnavailable === false && undecryptable > 0 && !opts?.log) process.stderr.write(`${count}
527
+ `);
330
528
  messages.reverse();
331
- const oldest = messages[0];
332
- return typeof res.end === "string" && oldest ? { messages, nextBefore: oldest.eventId } : { messages };
529
+ return {
530
+ messages,
531
+ ...typeof res.end === "string" && oldest ? { nextBefore: oldest } : {},
532
+ ...options?.includeUnavailable === false || options?.stopAtEventId ? { ...oldestTs === void 0 ? {} : { oldestTs }, reachedBoundary } : {}
533
+ };
333
534
  },
334
535
  async send(roomId, text) {
335
536
  if (stopped) throw new Error("session_stopped");
@@ -348,6 +549,35 @@ async function createAgentMatrixSession(creds, opts) {
348
549
  const value = joinedRoom && client.getRoom(joinedRoom)?.currentState.getStateEvents("m.room.member", userId)?.getContent().displayname;
349
550
  return typeof value === "string" && value.length > 0 ? value : void 0;
350
551
  },
552
+ members(roomId) {
553
+ return (client.getRoom(roomId)?.getJoinedMembers() ?? []).filter((member) => member.userId !== creds.userId).map((member) => {
554
+ const user = client.getUser(member.userId);
555
+ const event = user?.events.presence;
556
+ if (!user || !event) return { name: member.name, presence: "unknown" };
557
+ if (user.presence !== "offline") return { name: member.name, presence: "online" };
558
+ const reason = event.getContent().status_msg;
559
+ return {
560
+ name: member.name,
561
+ presence: "away",
562
+ since: new Date(user.lastPresenceTs - (user.lastActiveAgo ?? 0)).toISOString(),
563
+ ...typeof reason === "string" && reason ? { reason: reason.slice(0, 80) } : {}
564
+ };
565
+ });
566
+ },
567
+ seenBy(roomId, eventId) {
568
+ const room = client.getRoom(roomId);
569
+ if (!room?.findEventById(eventId)) return [];
570
+ return room.getJoinedMembers().filter((member) => member.userId !== creds.userId && room.hasUserReadEvent(member.userId, eventId)).map((member) => member.name);
571
+ },
572
+ setAway(reason) {
573
+ if (reason === away) return;
574
+ away = reason;
575
+ if (presenceTimer && !stopped) publishPresence();
576
+ },
577
+ async markRead(roomId, eventId) {
578
+ if (stopped) throw new Error("session_stopped");
579
+ await guarded(roomId, () => client.setRoomReadMarkersHttpRequest(roomId, eventId, eventId));
580
+ },
351
581
  stop
352
582
  };
353
583
  }