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.
- package/README.md +4 -4
- package/dist/THIRD_PARTY_NOTICES.txt +809 -0
- package/dist/chunk-2AMWQ7RB.mjs +1127 -0
- package/dist/chunk-3E5DH3Y6.mjs +9864 -0
- package/dist/{chunk-IQJQ4RF5.mjs → chunk-4BP7V3OP.mjs} +12 -1
- package/dist/{chunk-FV32AQTC.mjs → chunk-4BZQ4GVW.mjs} +10 -6
- package/dist/chunk-5JSGA6OB.mjs +1483 -0
- package/dist/chunk-6AQVW5W7.mjs +60 -0
- package/dist/{chunk-W6R4UJUQ.mjs → chunk-6J2PUM7L.mjs} +16 -9
- package/dist/chunk-6SUV3EW6.mjs +27 -0
- package/dist/{chunk-RNDUFTBF.mjs → chunk-ARIDZOTI.mjs} +499 -115
- package/dist/chunk-C2WEDMAF.mjs +15 -0
- package/dist/{chunk-Z4JF2M5C.mjs → chunk-CHC4VZF5.mjs} +9 -9
- package/dist/chunk-CKLCR5G4.mjs +1369 -0
- package/dist/chunk-EVT2DWNZ.mjs +58 -0
- package/dist/chunk-FJVVSKZ3.mjs +22 -0
- package/dist/{chunk-APMWDF6B.mjs → chunk-G3NMB4PY.mjs} +4 -2
- package/dist/chunk-HHEZVPRR.mjs +7 -0
- package/dist/{chunk-54XFVSEC.mjs → chunk-JKOJQTFT.mjs} +103 -5
- package/dist/{chunk-ZJ4W7P33.mjs → chunk-NDMFD5IM.mjs} +15 -10
- package/dist/{chunk-VKQ3KUYO.mjs → chunk-OEV5DFLW.mjs} +77 -7
- package/dist/chunk-OIZ4R5H7.mjs +173 -0
- package/dist/{chunk-CJTCU755.mjs → chunk-PJKI7SRI.mjs} +256 -26
- package/dist/chunk-PN25BUQ4.mjs +599 -0
- package/dist/chunk-QDPJ4VT2.mjs +432 -0
- package/dist/{chunk-2DHIAFEV.mjs → chunk-SRZTHCXP.mjs} +24 -10
- package/dist/chunk-TSK3HJEG.mjs +20 -0
- package/dist/chunk-UX34IVN5.mjs +33 -0
- package/dist/{chunk-BKLW2SPG.mjs → chunk-VAC3JTAV.mjs} +34 -17
- package/dist/{chunk-VAI7IUI4.mjs → chunk-VDZK4LW3.mjs} +30 -12
- package/dist/chunk-W6HM6F2U.mjs +901 -0
- package/dist/{chunk-H3K4GA2P.mjs → chunk-WPNMWNCL.mjs} +33 -21
- package/dist/chunk-WUDXHF2Q.mjs +14 -0
- package/dist/chunk-WZJ6DQJV.mjs +19 -0
- package/dist/{chunk-5LIE5GUU.mjs → chunk-XR625T3I.mjs} +5 -1
- package/dist/chunk-ZSKLNZWZ.mjs +224 -0
- package/dist/iterm2_send.py +56 -0
- package/dist/khala.mjs +20 -13
- package/dist/web/assets/local-CSPuGnH2.css +1 -0
- package/dist/web/assets/local-Dtyxaacx.js +9 -0
- package/dist/web/index.html +2 -2
- package/package.json +5 -3
- package/dist/chunk-2VCAW4GH.mjs +0 -34
- package/dist/chunk-5BQTYU66.mjs +0 -886
- package/dist/chunk-5GLTKWJ5.mjs +0 -1
- package/dist/chunk-5PUQT3YS.mjs +0 -569
- package/dist/chunk-7HBRFY3Z.mjs +0 -8
- package/dist/chunk-CBPLCCRZ.mjs +0 -191
- package/dist/chunk-FH34B3I3.mjs +0 -235
- package/dist/chunk-NKX3SLS7.mjs +0 -227
- package/dist/chunk-VUSV2ADL.mjs +0 -267
- package/dist/web/assets/local-BQl2KrE1.js +0 -9
- 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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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-
|
|
13
|
-
import
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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), () =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
332
|
-
|
|
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
|
}
|