khala-cli 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/THIRD_PARTY_NOTICES.txt +781 -0
- package/dist/{chunk-SU6362YU.mjs → chunk-3KDOTR54.mjs} +4 -2
- package/dist/{chunk-K6EJSZYO.mjs → chunk-3R6HFSLL.mjs} +149 -40
- package/dist/{chunk-OAFYPBCP.mjs → chunk-47XNLAJG.mjs} +5 -1
- package/dist/chunk-6BO3JSUP.mjs +33 -0
- package/dist/chunk-7R3EIDGR.mjs +58 -0
- package/dist/{chunk-YIHRHD7W.mjs → chunk-ANSR4IAW.mjs} +5 -1
- package/dist/chunk-BEO7WIIM.mjs +153 -0
- package/dist/chunk-BLCRVRIK.mjs +1000 -0
- package/dist/{chunk-LNG3ZZBK.mjs → chunk-BV45NK7K.mjs} +5 -1
- package/dist/{chunk-6YEYXZZB.mjs → chunk-DJM23SIU.mjs} +8 -8
- package/dist/{chunk-25AX7UMF.mjs → chunk-FO5GXCST.mjs} +24 -10
- package/dist/{chunk-IG2NU5J6.mjs → chunk-FZEH2IXZ.mjs} +95 -15
- package/dist/{chunk-TOBQUAPD.mjs → chunk-GSZADJMW.mjs} +253 -73
- package/dist/chunk-ITFD3JZS.mjs +1137 -0
- package/dist/chunk-K7V7X6WU.mjs +207 -0
- package/dist/chunk-KMLXOPBQ.mjs +20 -0
- package/dist/chunk-LBT47HLZ.mjs +19 -0
- package/dist/{chunk-3OFKVYOX.mjs → chunk-NGSYOVQ4.mjs} +12 -1
- package/dist/chunk-NIX5F4LT.mjs +863 -0
- package/dist/chunk-NPSQNCOY.mjs +590 -0
- package/dist/chunk-P3YIN7G7.mjs +26 -0
- package/dist/chunk-PAJ4JJPV.mjs +9827 -0
- package/dist/{chunk-ODTSGEID.mjs → chunk-PDZ5Z5ZE.mjs} +9 -6
- package/dist/chunk-QKWEZXR3.mjs +52 -0
- package/dist/chunk-SSO5RFOX.mjs +7 -0
- package/dist/{chunk-W5HS2PQO.mjs → chunk-T7IL7V7Q.mjs} +11 -9
- package/dist/{chunk-OP2HZ7EM.mjs → chunk-TANCKTDK.mjs} +24 -5
- package/dist/chunk-TEFOQXBK.mjs +44 -0
- package/dist/{chunk-7V2YQIRI.mjs → chunk-UK5GJO4Y.mjs} +22 -9
- package/dist/{chunk-C5SMOUTO.mjs → chunk-V4F3SYKW.mjs} +38 -11
- package/dist/{chunk-L7L6UNXN.mjs → chunk-WDOHRV3D.mjs} +31 -19
- package/dist/chunk-XBQDIKB5.mjs +18 -0
- package/dist/chunk-XUCWSYVS.mjs +148 -0
- package/dist/khala.mjs +20 -13
- package/dist/web/assets/local-CFzRHZpB.js +9 -0
- package/dist/web/assets/{local-OwDq47O4.css → local-ciypUFoK.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +5 -3
- package/dist/chunk-4LSCOWWN.mjs +0 -237
- package/dist/chunk-5GLTKWJ5.mjs +0 -1
- package/dist/chunk-5RGEO5HN.mjs +0 -142
- package/dist/chunk-APIWKRKG.mjs +0 -8
- package/dist/chunk-JEI32DEA.mjs +0 -634
- package/dist/chunk-S3FX7VCY.mjs +0 -386
- package/dist/chunk-U3MKSPRO.mjs +0 -227
- package/dist/chunk-WNOIHCCC.mjs +0 -191
- package/dist/web/assets/local-BQl2KrE1.js +0 -9
|
@@ -9,9 +9,13 @@ import {
|
|
|
9
9
|
utcTimestamp,
|
|
10
10
|
utf8Length,
|
|
11
11
|
version
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-NPSQNCOY.mjs";
|
|
13
|
+
import {
|
|
14
|
+
init_define_KHALA_BUNDLE
|
|
15
|
+
} from "./chunk-7R3EIDGR.mjs";
|
|
13
16
|
|
|
14
17
|
// ../contracts/src/m1/channel-event.ts
|
|
18
|
+
init_define_KHALA_BUNDLE();
|
|
15
19
|
var CHANNEL_EVENT_TYPE = "com.khala.event.v1";
|
|
16
20
|
var CHANNEL_EVENT_STATUSES = ["info", "pending", "success", "failure", "attention"];
|
|
17
21
|
var MAX_CHANNEL_EVENT_BYTES = 4096;
|
|
@@ -3,20 +3,20 @@ import {
|
|
|
3
3
|
WATCH_USAGE,
|
|
4
4
|
mentions,
|
|
5
5
|
monitorArmed,
|
|
6
|
+
monitorOwner,
|
|
6
7
|
run,
|
|
7
8
|
watchSession
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-U3MKSPRO.mjs";
|
|
15
|
-
import "./chunk-APIWKRKG.mjs";
|
|
9
|
+
} from "./chunk-NIX5F4LT.mjs";
|
|
10
|
+
import "./chunk-NGSYOVQ4.mjs";
|
|
11
|
+
import "./chunk-TANCKTDK.mjs";
|
|
12
|
+
import "./chunk-NPSQNCOY.mjs";
|
|
13
|
+
import "./chunk-K7V7X6WU.mjs";
|
|
14
|
+
import "./chunk-7R3EIDGR.mjs";
|
|
16
15
|
export {
|
|
17
16
|
WATCH_USAGE,
|
|
18
17
|
run as default,
|
|
19
18
|
mentions,
|
|
20
19
|
monitorArmed,
|
|
20
|
+
monitorOwner,
|
|
21
21
|
watchSession
|
|
22
22
|
};
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
KHALA_AGENT_VERSION
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ANSR4IAW.mjs";
|
|
5
5
|
import {
|
|
6
6
|
bundle
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import {
|
|
9
|
-
StateError,
|
|
10
|
-
ensureStateDir,
|
|
11
|
-
readJson,
|
|
12
|
-
stateRoot
|
|
13
|
-
} from "./chunk-WNOIHCCC.mjs";
|
|
7
|
+
} from "./chunk-3KDOTR54.mjs";
|
|
14
8
|
import {
|
|
15
9
|
LISTENING_MODES
|
|
16
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NGSYOVQ4.mjs";
|
|
17
11
|
import {
|
|
18
12
|
HARNESS_REGISTRY,
|
|
19
13
|
M1_LABEL_MAX_BYTES,
|
|
14
|
+
StateError,
|
|
20
15
|
array,
|
|
21
16
|
decodeWith,
|
|
22
17
|
displayText,
|
|
23
18
|
elementPath,
|
|
19
|
+
ensureStateDir,
|
|
24
20
|
fail,
|
|
25
21
|
harnessInfo,
|
|
26
22
|
identifier,
|
|
@@ -29,17 +25,23 @@ import {
|
|
|
29
25
|
object,
|
|
30
26
|
readHarness,
|
|
31
27
|
readHttpUrl,
|
|
28
|
+
readJson,
|
|
32
29
|
readMatrixUserId,
|
|
33
30
|
readRoomId,
|
|
34
31
|
reserved,
|
|
35
32
|
safeInteger,
|
|
33
|
+
stateRoot,
|
|
36
34
|
text,
|
|
37
35
|
utcTimestamp,
|
|
38
36
|
utf8Length,
|
|
39
37
|
version
|
|
40
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-NPSQNCOY.mjs";
|
|
39
|
+
import {
|
|
40
|
+
init_define_KHALA_BUNDLE
|
|
41
|
+
} from "./chunk-7R3EIDGR.mjs";
|
|
41
42
|
|
|
42
43
|
// src/client.ts
|
|
44
|
+
init_define_KHALA_BUNDLE();
|
|
43
45
|
var KhalaClientError = class extends Error {
|
|
44
46
|
constructor(code, message, extra) {
|
|
45
47
|
super(message ?? code);
|
|
@@ -51,7 +53,11 @@ var KhalaClientError = class extends Error {
|
|
|
51
53
|
code;
|
|
52
54
|
};
|
|
53
55
|
|
|
56
|
+
// ../contracts/src/m1/local.ts
|
|
57
|
+
init_define_KHALA_BUNDLE();
|
|
58
|
+
|
|
54
59
|
// ../contracts/src/m1/colors.ts
|
|
60
|
+
init_define_KHALA_BUNDLE();
|
|
55
61
|
var HUMAN_COLOR_IDS = ["red", "orange", "amber", "lime", "green", "teal", "blue", "indigo", "purple", "pink"];
|
|
56
62
|
function isHumanColorId(value) {
|
|
57
63
|
return typeof value === "string" && HUMAN_COLOR_IDS.includes(value);
|
|
@@ -73,6 +79,7 @@ function readHumanColorId(input, path2) {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
// ../contracts/src/m1/initials.ts
|
|
82
|
+
init_define_KHALA_BUNDLE();
|
|
76
83
|
function normalizeInitials(input) {
|
|
77
84
|
if (typeof input !== "string") return null;
|
|
78
85
|
const nfc = input.normalize("NFC");
|
|
@@ -88,6 +95,12 @@ function readHumanInitials(input, path2) {
|
|
|
88
95
|
return input;
|
|
89
96
|
}
|
|
90
97
|
|
|
98
|
+
// ../contracts/src/m1/names.ts
|
|
99
|
+
init_define_KHALA_BUNDLE();
|
|
100
|
+
|
|
101
|
+
// ../contracts/src/m1/participants.ts
|
|
102
|
+
init_define_KHALA_BUNDLE();
|
|
103
|
+
|
|
91
104
|
// ../contracts/src/m1/names.ts
|
|
92
105
|
var USERNAME_MIN = 2;
|
|
93
106
|
var USERNAME_MAX = 24;
|
|
@@ -488,6 +501,7 @@ function decodeChannelSecrets(input) {
|
|
|
488
501
|
}
|
|
489
502
|
|
|
490
503
|
// src/local/lifecycle.ts
|
|
504
|
+
init_define_KHALA_BUNDLE();
|
|
491
505
|
import * as fs from "node:fs/promises";
|
|
492
506
|
import { close as closeFd } from "node:fs";
|
|
493
507
|
import path from "node:path";
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-SSO5RFOX.mjs";
|
|
3
3
|
import {
|
|
4
4
|
CHANNEL_EVENT_TYPE,
|
|
5
5
|
encodeChannelEvent,
|
|
6
6
|
memberRenameContent
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BV45NK7K.mjs";
|
|
8
8
|
import {
|
|
9
9
|
LISTENING_MODE_COMMAND_TYPE,
|
|
10
10
|
LISTENING_MODE_MEMBER_KEY,
|
|
11
11
|
memberListeningMode
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import
|
|
14
|
-
|
|
12
|
+
} from "./chunk-NGSYOVQ4.mjs";
|
|
13
|
+
import {
|
|
14
|
+
StateError
|
|
15
|
+
} from "./chunk-NPSQNCOY.mjs";
|
|
16
|
+
import {
|
|
17
|
+
init_define_KHALA_BUNDLE
|
|
18
|
+
} from "./chunk-7R3EIDGR.mjs";
|
|
15
19
|
|
|
16
20
|
// src/matrix/session.ts
|
|
21
|
+
init_define_KHALA_BUNDLE();
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
17
23
|
import { createClient, ClientEvent, RoomEvent, MatrixEventEvent, SyncState, Direction, Method, EventType } from "matrix-js-sdk";
|
|
18
24
|
function message(event) {
|
|
19
25
|
const type = event.getType();
|
|
@@ -36,8 +42,10 @@ function message(event) {
|
|
|
36
42
|
}
|
|
37
43
|
async function createAgentMatrixSession(creds, opts) {
|
|
38
44
|
const started = Date.now();
|
|
45
|
+
const rejoinTxnId = `khala.rejoin.${randomUUID()}`;
|
|
39
46
|
const log = (line) => opts?.log?.(line);
|
|
40
|
-
const
|
|
47
|
+
const persistent = opts?.cryptoStore ? await (await import("./chunk-PAJ4JJPV.mjs")).openCryptoStore(opts.cryptoStore.dir, opts.cryptoStore.root, creds) : void 0;
|
|
48
|
+
const client = createClient({ ...persistent ? { store: persistent.sync } : {}, baseUrl: creds.homeserver, userId: creds.userId, accessToken: creds.accessToken, deviceId: creds.deviceId });
|
|
41
49
|
const handlers = /* @__PURE__ */ new Set();
|
|
42
50
|
const ended = /* @__PURE__ */ new Set();
|
|
43
51
|
const modeHandlers = /* @__PURE__ */ new Set();
|
|
@@ -48,8 +56,9 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
48
56
|
const cancellations = /* @__PURE__ */ new Set();
|
|
49
57
|
let joinedRoom;
|
|
50
58
|
let stopped = false;
|
|
59
|
+
let recovering = persistent?.restored ?? false;
|
|
51
60
|
const deliver = (event) => {
|
|
52
|
-
if (stopped || !liveEvents.has(event)) return;
|
|
61
|
+
if (stopped || recovering || !liveEvents.has(event)) return;
|
|
53
62
|
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
63
|
const m = command ?? message(event);
|
|
55
64
|
if (!m) {
|
|
@@ -85,7 +94,7 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
85
94
|
}
|
|
86
95
|
};
|
|
87
96
|
const timeline = (event, _room, toStart, _removed, data) => {
|
|
88
|
-
if (stopped || toStart || data?.liveEvent === false) return;
|
|
97
|
+
if (stopped || toStart || data?.liveEvent === false && !persistent?.restored) return;
|
|
89
98
|
liveEvents.add(event);
|
|
90
99
|
void client.decryptEventIfNeeded(event).then(() => deliver(event), () => log("live_decryption_failed"));
|
|
91
100
|
};
|
|
@@ -152,9 +161,35 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
152
161
|
check();
|
|
153
162
|
});
|
|
154
163
|
};
|
|
155
|
-
|
|
164
|
+
let stopping;
|
|
165
|
+
let wipe = false;
|
|
166
|
+
const stop = () => stopping ??= stopSession();
|
|
167
|
+
const stopSession = async () => {
|
|
156
168
|
if (stopped) return;
|
|
157
169
|
stopped = true;
|
|
170
|
+
if (wipe && persistent?.forgetIdentity) {
|
|
171
|
+
try {
|
|
172
|
+
await persistent.forgetIdentity();
|
|
173
|
+
} catch {
|
|
174
|
+
log("crypto_cleanup_failed");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
let syncStopped;
|
|
178
|
+
if (persistent && client.getSyncState() !== null && client.getSyncState() !== SyncState.Stopped) {
|
|
179
|
+
syncStopped = new Promise((resolve, reject) => {
|
|
180
|
+
const done = (state) => {
|
|
181
|
+
if (state === SyncState.Stopped) finish();
|
|
182
|
+
};
|
|
183
|
+
const timer = setTimeout(() => finish(new Error("crypto_sync_stop_timeout")), 1e4);
|
|
184
|
+
const finish = (error) => {
|
|
185
|
+
clearTimeout(timer);
|
|
186
|
+
client.removeListener(ClientEvent.Sync, done);
|
|
187
|
+
if (error) reject(error);
|
|
188
|
+
else resolve();
|
|
189
|
+
};
|
|
190
|
+
client.on(ClientEvent.Sync, done);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
158
193
|
client.stopClient();
|
|
159
194
|
client.removeListener(RoomEvent.Timeline, timeline);
|
|
160
195
|
client.removeListener(MatrixEventEvent.Decrypted, decrypted);
|
|
@@ -166,6 +201,14 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
166
201
|
handlers.clear();
|
|
167
202
|
modeHandlers.clear();
|
|
168
203
|
ended.clear();
|
|
204
|
+
if (persistent) {
|
|
205
|
+
try {
|
|
206
|
+
await syncStopped;
|
|
207
|
+
if (wipe) await persistent.wipe();
|
|
208
|
+
} finally {
|
|
209
|
+
await persistent.close();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
169
212
|
};
|
|
170
213
|
const membershipEnded = (room, membership) => {
|
|
171
214
|
if (stopped || room.roomId !== joinedRoom || !["leave", "ban"].includes(membership)) return;
|
|
@@ -174,7 +217,11 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
174
217
|
const end = (reason) => {
|
|
175
218
|
if (stopped) return;
|
|
176
219
|
const listeners = [...ended];
|
|
177
|
-
|
|
220
|
+
wipe = true;
|
|
221
|
+
void stop().catch(() => {
|
|
222
|
+
log("crypto_cleanup_failed");
|
|
223
|
+
console.error("khala: crypto_cleanup_failed");
|
|
224
|
+
});
|
|
178
225
|
for (const handler of listeners) {
|
|
179
226
|
try {
|
|
180
227
|
handler(reason);
|
|
@@ -184,7 +231,14 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
184
231
|
}
|
|
185
232
|
};
|
|
186
233
|
try {
|
|
187
|
-
await
|
|
234
|
+
if (persistent) await persistent.sync.startup();
|
|
235
|
+
try {
|
|
236
|
+
await client.initRustCrypto(persistent ? { useIndexedDB: true, cryptoDatabasePrefix: persistent.prefix } : { useIndexedDB: false });
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (!persistent?.restored || error instanceof StateError && error.code === "unsafe_state_dir") throw error;
|
|
239
|
+
wipe = true;
|
|
240
|
+
throw new (await import("./chunk-PAJ4JJPV.mjs")).CryptoStoreCorruptError();
|
|
241
|
+
}
|
|
188
242
|
const crypto = client.getCrypto();
|
|
189
243
|
if (!crypto) throw new Error("crypto_unavailable");
|
|
190
244
|
log(`crypto_version=${crypto.getVersion()}`);
|
|
@@ -227,6 +281,7 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
227
281
|
}, ready, timeoutMs, error);
|
|
228
282
|
return {
|
|
229
283
|
userId: creds.userId,
|
|
284
|
+
...persistent?.recovered ? { cryptoReset: true } : {},
|
|
230
285
|
onEnded(handler) {
|
|
231
286
|
if (!stopped) ended.add(handler);
|
|
232
287
|
return () => {
|
|
@@ -277,16 +332,41 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
277
332
|
const owner = ownJoin.getContent()["com.khala.invited_by"];
|
|
278
333
|
if (typeof owner === "string") inviters.set(roomId, owner);
|
|
279
334
|
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,
|
|
335
|
+
if (event.ok) await client.sendEvent(roomId, CHANNEL_EVENT_TYPE, event.value, rejoinTxnId);
|
|
281
336
|
}
|
|
282
337
|
if (membership !== "join" && inviters.has(roomId)) {
|
|
283
338
|
const content = { ...ownJoin.getContent(), membership: "join", "com.khala.invited_by": inviters.get(roomId) };
|
|
284
339
|
await client.sendStateEvent(roomId, EventType.RoomMember, content, creds.userId);
|
|
285
340
|
}
|
|
286
|
-
|
|
341
|
+
const joinedAt = persistent?.joinedAt ?? (membership === "join" ? rejoinedAt : ownJoin.getTs());
|
|
342
|
+
await persistent?.rememberJoin(joinedAt);
|
|
343
|
+
joinTimes.set(roomId, joinedAt);
|
|
344
|
+
if (persistent?.restored) {
|
|
345
|
+
let token = null;
|
|
346
|
+
const seenTokens = /* @__PURE__ */ new Set();
|
|
347
|
+
for (; ; ) {
|
|
348
|
+
if (stopped) throw new Error("session_stopped");
|
|
349
|
+
const page = await client.createMessagesRequest(roomId, token, 100, Direction.Backward);
|
|
350
|
+
let reachedJoin = false;
|
|
351
|
+
for (const raw of page.chunk) {
|
|
352
|
+
const event = client.getEventMapper()({ ...raw, room_id: roomId });
|
|
353
|
+
if (event.getTs() < joinedAt) {
|
|
354
|
+
reachedJoin = true;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
liveEvents.add(event);
|
|
358
|
+
await client.decryptEventIfNeeded(event).catch(() => log("recovery_decryption_failed"));
|
|
359
|
+
}
|
|
360
|
+
if (reachedJoin || page.chunk.length === 0 || !page.end) break;
|
|
361
|
+
if (seenTokens.has(page.end)) throw new Error("recovery_pagination_stalled");
|
|
362
|
+
seenTokens.add(page.end);
|
|
363
|
+
token = page.end;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
recovering = false;
|
|
287
367
|
if (!joinedRoom) client.on(RoomEvent.MyMembership, membershipEnded);
|
|
288
368
|
joinedRoom = roomId;
|
|
289
|
-
for (const event of liveEvents) {
|
|
369
|
+
for (const event of [...liveEvents].sort((a, b) => a.getTs() - b.getTs())) {
|
|
290
370
|
if (event.getRoomId() === roomId) deliver(event);
|
|
291
371
|
}
|
|
292
372
|
},
|
|
@@ -310,7 +390,7 @@ async function createAgentMatrixSession(creds, opts) {
|
|
|
310
390
|
undecryptable++;
|
|
311
391
|
const eventId = event.getId(), sender = event.getSender();
|
|
312
392
|
if (eventId && sender && eventId !== before) {
|
|
313
|
-
const body = "[Encrypted message unavailable: this device does not have its key.
|
|
393
|
+
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
394
|
messages.push({
|
|
315
395
|
eventId,
|
|
316
396
|
roomId,
|