khala-cli 0.4.9 → 0.4.11

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 (48) hide show
  1. package/dist/THIRD_PARTY_NOTICES.txt +28 -0
  2. package/dist/{chunk-DJM23SIU.mjs → chunk-3KHLSF4A.mjs} +7 -8
  3. package/dist/{chunk-PDZ5Z5ZE.mjs → chunk-4CENUF7M.mjs} +24 -9
  4. package/dist/{chunk-QKWEZXR3.mjs → chunk-4PUNVPC3.mjs} +12 -4
  5. package/dist/{chunk-WDOHRV3D.mjs → chunk-7FFHZ7EI.mjs} +14 -14
  6. package/dist/chunk-AQX2OYEF.mjs +19 -0
  7. package/dist/{chunk-ITFD3JZS.mjs → chunk-BVG7PJNZ.mjs} +424 -78
  8. package/dist/{chunk-NGSYOVQ4.mjs → chunk-CXVANPY5.mjs} +2 -2
  9. package/dist/chunk-CYINL2V3.mjs +20 -0
  10. package/dist/{chunk-KMLXOPBQ.mjs → chunk-E5QRD6W3.mjs} +4 -2
  11. package/dist/{chunk-FO5GXCST.mjs → chunk-FGHPOKXG.mjs} +5 -5
  12. package/dist/{chunk-FZEH2IXZ.mjs → chunk-GVZD4TLW.mjs} +180 -26
  13. package/dist/{chunk-V4F3SYKW.mjs → chunk-H2L5GQ5W.mjs} +25 -17
  14. package/dist/chunk-HZH74L5T.mjs +901 -0
  15. package/dist/{chunk-NPSQNCOY.mjs → chunk-JNSB3E6C.mjs} +13 -4
  16. package/dist/{chunk-7R3EIDGR.mjs → chunk-LDW4HOQW.mjs} +1 -1
  17. package/dist/chunk-LNKI3CZO.mjs +15 -0
  18. package/dist/{chunk-TEFOQXBK.mjs → chunk-MAIMFERG.mjs} +3 -3
  19. package/dist/chunk-MYIZQCRW.mjs +27 -0
  20. package/dist/{chunk-TANCKTDK.mjs → chunk-N6UYX3SY.mjs} +72 -5
  21. package/dist/{chunk-6BO3JSUP.mjs → chunk-NE4S7N3R.mjs} +2 -2
  22. package/dist/{chunk-BLCRVRIK.mjs → chunk-NJ5ICCVZ.mjs} +456 -87
  23. package/dist/{chunk-3R6HFSLL.mjs → chunk-NN5RHU6C.mjs} +92 -38
  24. package/dist/{chunk-NIX5F4LT.mjs → chunk-NOKTVPSY.mjs} +330 -66
  25. package/dist/{chunk-3KDOTR54.mjs → chunk-NUQSDSVT.mjs} +1 -1
  26. package/dist/{chunk-BEO7WIIM.mjs → chunk-OSVXLZKJ.mjs} +28 -8
  27. package/dist/{chunk-SSO5RFOX.mjs → chunk-R5NDTJRA.mjs} +1 -1
  28. package/dist/{chunk-K7V7X6WU.mjs → chunk-TVQ4MSEI.mjs} +21 -4
  29. package/dist/{chunk-BV45NK7K.mjs → chunk-VWWI4S5A.mjs} +100 -6
  30. package/dist/{chunk-UK5GJO4Y.mjs → chunk-WA5LB5GN.mjs} +6 -5
  31. package/dist/chunk-X3BAMHAR.mjs +14 -0
  32. package/dist/{chunk-PAJ4JJPV.mjs → chunk-YDEZFVF7.mjs} +43 -6
  33. package/dist/{chunk-GSZADJMW.mjs → chunk-YFX5NDSL.mjs} +338 -81
  34. package/dist/{chunk-ANSR4IAW.mjs → chunk-YIKJI26V.mjs} +2 -2
  35. package/dist/{chunk-T7IL7V7Q.mjs → chunk-YNUCRFM2.mjs} +13 -10
  36. package/dist/iterm2_send.py +56 -0
  37. package/dist/khala.mjs +11 -11
  38. package/dist/web/assets/local-DPcMk9Xz.css +1 -0
  39. package/dist/web/assets/local-SDOFp8OA.js +9 -0
  40. package/dist/web/index.html +2 -2
  41. package/package.json +1 -1
  42. package/dist/chunk-47XNLAJG.mjs +0 -38
  43. package/dist/chunk-LBT47HLZ.mjs +0 -19
  44. package/dist/chunk-P3YIN7G7.mjs +0 -26
  45. package/dist/chunk-XBQDIKB5.mjs +0 -18
  46. package/dist/chunk-XUCWSYVS.mjs +0 -148
  47. package/dist/web/assets/local-CFzRHZpB.js +0 -9
  48. package/dist/web/assets/local-ciypUFoK.css +0 -1
@@ -5,10 +5,10 @@ import {
5
5
  object,
6
6
  readMatrixUserId,
7
7
  version
8
- } from "./chunk-NPSQNCOY.mjs";
8
+ } from "./chunk-JNSB3E6C.mjs";
9
9
  import {
10
10
  init_define_KHALA_BUNDLE
11
- } from "./chunk-7R3EIDGR.mjs";
11
+ } from "./chunk-LDW4HOQW.mjs";
12
12
 
13
13
  // ../contracts/src/m1/listening-mode.ts
14
14
  init_define_KHALA_BUNDLE();
@@ -0,0 +1,20 @@
1
+ import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
+ import {
3
+ resolveHarness,
4
+ resolveSession,
5
+ resolveSessionId
6
+ } from "./chunk-NE4S7N3R.mjs";
7
+ import "./chunk-BVG7PJNZ.mjs";
8
+ import "./chunk-4PUNVPC3.mjs";
9
+ import "./chunk-NOKTVPSY.mjs";
10
+ import "./chunk-CXVANPY5.mjs";
11
+ import "./chunk-N6UYX3SY.mjs";
12
+ import "./chunk-VWWI4S5A.mjs";
13
+ import "./chunk-JNSB3E6C.mjs";
14
+ import "./chunk-TVQ4MSEI.mjs";
15
+ import "./chunk-LDW4HOQW.mjs";
16
+ export {
17
+ resolveHarness,
18
+ resolveSession,
19
+ resolveSessionId
20
+ };
@@ -1,16 +1,18 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
2
  import {
3
3
  cancelAttempt,
4
+ confirmWakeText,
4
5
  failAttempt,
5
6
  readWakeState,
6
7
  recordAttempt,
7
8
  resetWakeDriver,
8
9
  settleAttempts,
9
10
  takeWakeDisableNotices
10
- } from "./chunk-K7V7X6WU.mjs";
11
- import "./chunk-7R3EIDGR.mjs";
11
+ } from "./chunk-TVQ4MSEI.mjs";
12
+ import "./chunk-LDW4HOQW.mjs";
12
13
  export {
13
14
  cancelAttempt,
15
+ confirmWakeText,
14
16
  failAttempt,
15
17
  readWakeState,
16
18
  recordAttempt,
@@ -1,13 +1,13 @@
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-ANSR4IAW.mjs";
4
+ } from "./chunk-YIKJI26V.mjs";
5
5
  import {
6
6
  bundle
7
- } from "./chunk-3KDOTR54.mjs";
7
+ } from "./chunk-NUQSDSVT.mjs";
8
8
  import {
9
9
  LISTENING_MODES
10
- } from "./chunk-NGSYOVQ4.mjs";
10
+ } from "./chunk-CXVANPY5.mjs";
11
11
  import {
12
12
  HARNESS_REGISTRY,
13
13
  M1_LABEL_MAX_BYTES,
@@ -35,10 +35,10 @@ import {
35
35
  utcTimestamp,
36
36
  utf8Length,
37
37
  version
38
- } from "./chunk-NPSQNCOY.mjs";
38
+ } from "./chunk-JNSB3E6C.mjs";
39
39
  import {
40
40
  init_define_KHALA_BUNDLE
41
- } from "./chunk-7R3EIDGR.mjs";
41
+ } from "./chunk-LDW4HOQW.mjs";
42
42
 
43
43
  // src/client.ts
44
44
  init_define_KHALA_BUNDLE();
@@ -1,26 +1,32 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
- import "./chunk-SSO5RFOX.mjs";
3
2
  import {
4
- CHANNEL_EVENT_TYPE,
5
- encodeChannelEvent,
6
- memberRenameContent
7
- } from "./chunk-BV45NK7K.mjs";
3
+ retryUnexpired
4
+ } from "./chunk-LNKI3CZO.mjs";
5
+ import {
6
+ RESTORE_HISTORY_MAX_PAGES
7
+ } from "./chunk-X3BAMHAR.mjs";
8
+ import "./chunk-R5NDTJRA.mjs";
8
9
  import {
9
10
  LISTENING_MODE_COMMAND_TYPE,
10
11
  LISTENING_MODE_MEMBER_KEY,
11
12
  memberListeningMode
12
- } from "./chunk-NGSYOVQ4.mjs";
13
+ } from "./chunk-CXVANPY5.mjs";
14
+ import {
15
+ CHANNEL_EVENT_TYPE,
16
+ encodeChannelEvent,
17
+ memberRenameContent
18
+ } from "./chunk-VWWI4S5A.mjs";
13
19
  import {
14
20
  StateError
15
- } from "./chunk-NPSQNCOY.mjs";
21
+ } from "./chunk-JNSB3E6C.mjs";
16
22
  import {
17
23
  init_define_KHALA_BUNDLE
18
- } from "./chunk-7R3EIDGR.mjs";
24
+ } from "./chunk-LDW4HOQW.mjs";
19
25
 
20
26
  // src/matrix/session.ts
21
27
  init_define_KHALA_BUNDLE();
22
28
  import { randomUUID } from "node:crypto";
23
- import { createClient, ClientEvent, RoomEvent, MatrixEventEvent, SyncState, Direction, Method, EventType } from "matrix-js-sdk";
29
+ import { createClient, ClientEvent, RoomEvent, MatrixEventEvent, SyncState, SetPresence, Direction, Method, EventType } from "matrix-js-sdk";
24
30
  function message(event) {
25
31
  const type = event.getType();
26
32
  const eventId = event.getId();
@@ -44,7 +50,7 @@ async function createAgentMatrixSession(creds, opts) {
44
50
  const started = Date.now();
45
51
  const rejoinTxnId = `khala.rejoin.${randomUUID()}`;
46
52
  const log = (line) => opts?.log?.(line);
47
- const persistent = opts?.cryptoStore ? await (await import("./chunk-PAJ4JJPV.mjs")).openCryptoStore(opts.cryptoStore.dir, opts.cryptoStore.root, creds) : void 0;
53
+ const persistent = opts?.cryptoStore ? await (await import("./chunk-YDEZFVF7.mjs")).openCryptoStore(opts.cryptoStore.dir, opts.cryptoStore.root, creds) : void 0;
48
54
  const client = createClient({ ...persistent ? { store: persistent.sync } : {}, baseUrl: creds.homeserver, userId: creds.userId, accessToken: creds.accessToken, deviceId: creds.deviceId });
49
55
  const handlers = /* @__PURE__ */ new Set();
50
56
  const ended = /* @__PURE__ */ new Set();
@@ -52,13 +58,38 @@ async function createAgentMatrixSession(creds, opts) {
52
58
  const inviters = /* @__PURE__ */ new Map();
53
59
  const emitted = /* @__PURE__ */ new Set();
54
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
+ };
55
78
  const joinTimes = /* @__PURE__ */ new Map();
56
79
  const cancellations = /* @__PURE__ */ new Set();
57
80
  let joinedRoom;
58
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
+ };
59
88
  let recovering = persistent?.restored ?? false;
60
89
  const deliver = (event) => {
61
- if (stopped || recovering || !liveEvents.has(event)) return;
90
+ if (stopped) return;
91
+ if (liveEvents.has(event)) rememberDecryption(event);
92
+ if (recovering || !liveEvents.has(event)) return;
62
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;
63
94
  const m = command ?? message(event);
64
95
  if (!m) {
@@ -96,7 +127,10 @@ async function createAgentMatrixSession(creds, opts) {
96
127
  const timeline = (event, _room, toStart, _removed, data) => {
97
128
  if (stopped || toStart || data?.liveEvent === false && !persistent?.restored) return;
98
129
  liveEvents.add(event);
99
- 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
+ });
100
134
  };
101
135
  const decrypted = (event) => deliver(event);
102
136
  const syncLog = (state, _previous, data) => {
@@ -167,6 +201,24 @@ async function createAgentMatrixSession(creds, opts) {
167
201
  const stopSession = async () => {
168
202
  if (stopped) return;
169
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
+ }
170
222
  if (wipe && persistent?.forgetIdentity) {
171
223
  try {
172
224
  await persistent.forgetIdentity();
@@ -236,8 +288,7 @@ async function createAgentMatrixSession(creds, opts) {
236
288
  await client.initRustCrypto(persistent ? { useIndexedDB: true, cryptoDatabasePrefix: persistent.prefix } : { useIndexedDB: false });
237
289
  } catch (error) {
238
290
  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();
291
+ throw new (await import("./chunk-YDEZFVF7.mjs")).CryptoStoreCorruptError();
241
292
  }
242
293
  const crypto = client.getCrypto();
243
294
  if (!crypto) throw new Error("crypto_unavailable");
@@ -264,6 +315,9 @@ async function createAgentMatrixSession(creds, opts) {
264
315
  }, () => client.getSyncState() === SyncState.Prepared || client.getSyncState() === SyncState.Syncing, 3e4, "sync_timeout");
265
316
  await Promise.all([client.startClient({ initialSyncLimit: 30 }), prepared]);
266
317
  log(`session_ready_ms=${Date.now() - started}`);
318
+ publishPresence();
319
+ presenceTimer = setInterval(publishPresence, 6e4);
320
+ presenceTimer.unref();
267
321
  } catch (error) {
268
322
  await stop();
269
323
  throw error;
@@ -282,6 +336,7 @@ async function createAgentMatrixSession(creds, opts) {
282
336
  return {
283
337
  userId: creds.userId,
284
338
  ...persistent?.recovered ? { cryptoReset: true } : {},
339
+ ...persistent?.restored ? { recoversOnJoin: true } : {},
285
340
  onEnded(handler) {
286
341
  if (!stopped) ended.add(handler);
287
342
  return () => {
@@ -317,7 +372,6 @@ async function createAgentMatrixSession(creds, opts) {
317
372
  async join(roomId) {
318
373
  if (stopped) throw new Error("session_stopped");
319
374
  if (joinedRoom === roomId) return;
320
- const rejoinedAt = Date.now();
321
375
  const membership = client.getRoom(roomId)?.getMyMembership();
322
376
  if (membership !== "join") {
323
377
  if (membership !== "invite") throw new Error("not_invited");
@@ -338,30 +392,80 @@ async function createAgentMatrixSession(creds, opts) {
338
392
  const content = { ...ownJoin.getContent(), membership: "join", "com.khala.invited_by": inviters.get(roomId) };
339
393
  await client.sendStateEvent(roomId, EventType.RoomMember, content, creds.userId);
340
394
  }
341
- const joinedAt = persistent?.joinedAt ?? (membership === "join" ? rejoinedAt : ownJoin.getTs());
395
+ const joinedAt = persistent?.joinedAt ?? ownJoin.getTs();
342
396
  await persistent?.rememberJoin(joinedAt);
343
397
  joinTimes.set(roomId, joinedAt);
344
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();
345
428
  let token = null;
346
429
  const seenTokens = /* @__PURE__ */ new Set();
430
+ let pages = 0;
431
+ let undecryptable = 0;
347
432
  for (; ; ) {
348
433
  if (stopped) throw new Error("session_stopped");
349
434
  const page = await client.createMessagesRequest(roomId, token, 100, Direction.Backward);
350
- let reachedJoin = false;
435
+ pages++;
436
+ let reachedBoundary = false;
351
437
  for (const raw of page.chunk) {
352
438
  const event = client.getEventMapper()({ ...raw, room_id: roomId });
353
- if (event.getTs() < joinedAt) {
354
- reachedJoin = true;
355
- continue;
439
+ if (opts?.restoreStopAtEventId && event.getId() === opts.restoreStopAtEventId || event.getTs() < joinedAt) {
440
+ reachedBoundary = true;
441
+ break;
356
442
  }
357
443
  liveEvents.add(event);
358
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;
359
458
  }
360
- if (reachedJoin || page.chunk.length === 0 || !page.end) break;
361
459
  if (seenTokens.has(page.end)) throw new Error("recovery_pagination_stalled");
362
460
  seenTokens.add(page.end);
363
461
  token = page.end;
364
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
+ }
365
469
  }
366
470
  recovering = false;
367
471
  if (!joinedRoom) client.on(RoomEvent.MyMembership, membershipEnded);
@@ -370,7 +474,8 @@ async function createAgentMatrixSession(creds, opts) {
370
474
  if (event.getRoomId() === roomId) deliver(event);
371
475
  }
372
476
  },
373
- async history(roomId, limit, before) {
477
+ joinedAt: (roomId) => joinTimes.get(roomId),
478
+ async history(roomId, limit, before, options) {
374
479
  if (stopped) throw new Error("session_stopped");
375
480
  let token = null;
376
481
  if (before !== void 0) {
@@ -380,16 +485,26 @@ async function createAgentMatrixSession(creds, opts) {
380
485
  const res = await guarded(roomId, () => client.createMessagesRequest(roomId, token, limit, Direction.Backward));
381
486
  const messages = [];
382
487
  let undecryptable = 0;
488
+ let oldest;
489
+ let oldestTs;
490
+ let reachedBoundary = false;
383
491
  for (const raw of res.chunk) {
384
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
+ }
385
499
  try {
386
500
  await client.decryptEventIfNeeded(event);
387
501
  } catch {
388
502
  }
503
+ rememberDecryption(event);
389
504
  if (event.getType() === "m.room.encrypted" || event.isDecryptionFailure()) {
390
505
  undecryptable++;
391
506
  const eventId = event.getId(), sender = event.getSender();
392
- if (eventId && sender && eventId !== before) {
507
+ if (options?.includeUnavailable !== false && eventId && sender && eventId !== before) {
393
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.]";
394
509
  messages.push({
395
510
  eventId,
@@ -406,10 +521,16 @@ async function createAgentMatrixSession(creds, opts) {
406
521
  const m = message(event);
407
522
  if (m && m.eventId !== before) messages.push(m);
408
523
  }
409
- 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
+ `);
410
528
  messages.reverse();
411
- const oldest = messages[0];
412
- 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
+ };
413
534
  },
414
535
  async send(roomId, text) {
415
536
  if (stopped) throw new Error("session_stopped");
@@ -428,6 +549,39 @@ async function createAgentMatrixSession(creds, opts) {
428
549
  const value = joinedRoom && client.getRoom(joinedRoom)?.currentState.getStateEvents("m.room.member", userId)?.getContent().displayname;
429
550
  return typeof value === "string" && value.length > 0 ? value : void 0;
430
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
+ },
581
+ async setTyping(roomId, typing) {
582
+ if (stopped) return;
583
+ await client.sendTyping(roomId, typing, 3e4);
584
+ },
431
585
  stop
432
586
  };
433
587
  }
@@ -1,25 +1,30 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
2
  import {
3
3
  sessionStartWakeChannels
4
- } from "./chunk-TEFOQXBK.mjs";
4
+ } from "./chunk-MAIMFERG.mjs";
5
5
  import {
6
6
  adapterFor
7
- } from "./chunk-ITFD3JZS.mjs";
8
- import "./chunk-QKWEZXR3.mjs";
7
+ } from "./chunk-BVG7PJNZ.mjs";
8
+ import "./chunk-4PUNVPC3.mjs";
9
9
  import {
10
- driverAllowed
11
- } from "./chunk-NIX5F4LT.mjs";
12
- import "./chunk-NGSYOVQ4.mjs";
13
- import "./chunk-TANCKTDK.mjs";
10
+ driverAllowed,
11
+ readListeningMode
12
+ } from "./chunk-NOKTVPSY.mjs";
13
+ import "./chunk-CXVANPY5.mjs";
14
+ import {
15
+ wakesAgent
16
+ } from "./chunk-N6UYX3SY.mjs";
17
+ import "./chunk-VWWI4S5A.mjs";
14
18
  import {
15
19
  filesForDir,
20
+ readJson,
16
21
  resolveStateDir,
17
22
  stateRoot
18
- } from "./chunk-NPSQNCOY.mjs";
19
- import "./chunk-K7V7X6WU.mjs";
23
+ } from "./chunk-JNSB3E6C.mjs";
24
+ import "./chunk-TVQ4MSEI.mjs";
20
25
  import {
21
26
  init_define_KHALA_BUNDLE
22
- } from "./chunk-7R3EIDGR.mjs";
27
+ } from "./chunk-LDW4HOQW.mjs";
23
28
 
24
29
  // hooks/claude-wake.ts
25
30
  init_define_KHALA_BUNDLE();
@@ -30,7 +35,7 @@ import { setTimeout as sleep } from "node:timers/promises";
30
35
  var NOTICE = "Khala: new channel messages. They arrive in the next hook context.\n";
31
36
  var DEADLINE_MS = 24 * 60 * 60 * 1e3;
32
37
  var POLL_MS = 500;
33
- async function readJson(file) {
38
+ async function readJson2(file) {
34
39
  try {
35
40
  return JSON.parse(await fs.readFile(file, "utf8"));
36
41
  } catch (error) {
@@ -39,7 +44,10 @@ async function readJson(file) {
39
44
  }
40
45
  }
41
46
  async function unreadChannelMessages(dir) {
42
- const cursor = await readJson(path.join(dir, "cursor.json"));
47
+ const files = filesForDir(dir);
48
+ const mode = await readListeningMode(files);
49
+ const you = (await readJson(files.status))?.displayName;
50
+ const cursor = await readJson2(path.join(dir, "cursor.json"));
43
51
  const delivered = cursor && (cursor.lastDeliveredEventId === null || typeof cursor.lastDeliveredEventId === "string") && Number.isSafeInteger(cursor.deliveredCount) && typeof cursor.deliveredCount === "number" && cursor.deliveredCount >= 0 ? cursor.deliveredCount : 0;
44
52
  let content;
45
53
  try {
@@ -57,7 +65,7 @@ async function unreadChannelMessages(dir) {
57
65
  continue;
58
66
  }
59
67
  if (!entry || !["eventId", "roomId", "ts", "sender", "senderLabel", "body"].every((key) => typeof entry[key] === "string") || !["human", "agent", "unknown"].includes(entry.senderKind) || !["message", "event"].includes(entry.kind)) continue;
60
- if (index++ >= delivered && entry.kind === "message") messages++;
68
+ if (index++ >= delivered && wakesAgent(entry, mode, you)) messages++;
61
69
  }
62
70
  return messages;
63
71
  }
@@ -72,7 +80,7 @@ async function unreadMessages(dir) {
72
80
  }
73
81
  let messages = 0;
74
82
  for (const directory of directories) {
75
- if ((await readJson(path.join(directory, "mode.json")))?.mode === "async") continue;
83
+ if ((await readJson2(path.join(directory, "mode.json")))?.mode === "async") continue;
76
84
  messages += await unreadChannelMessages(directory);
77
85
  }
78
86
  return messages;
@@ -128,16 +136,16 @@ async function watch(stdin, _argv, io = { stderr: process.stderr, env: process.e
128
136
  };
129
137
  const deadline = started.getTime() + testDuration(io.env.KHALA_WAKE_TEST_DEADLINE_MS, DEADLINE_MS);
130
138
  const poll = testDuration(io.env.KHALA_WAKE_TEST_POLL_MS, POLL_MS);
131
- const owns = async () => (await readJson(owner))?.nonce === nonce;
139
+ const owns = async () => (await readJson2(owner))?.nonce === nonce;
132
140
  const idle = async () => {
133
- const activity = await readJson(path.join(dir, "activity.json"));
141
+ const activity = await readJson2(path.join(dir, "activity.json"));
134
142
  return activity?.state === "idle" && typeof activity.updatedAt === "string" && Number.isFinite(Date.parse(activity.updatedAt));
135
143
  };
136
144
  const unread = async () => {
137
145
  if (input.hook_event_name !== "SessionStart") return unreadMessages(dir);
138
146
  let messages = 0;
139
147
  for (const channel of await sessionStartWakeChannels(filesForDir(dir), io.env)) {
140
- if (channel.connected && (await readJson(channel.files.mode))?.mode !== "async") messages += await unreadChannelMessages(channel.files.dir);
148
+ if (channel.connected && (await readJson2(channel.files.mode))?.mode !== "async") messages += await unreadChannelMessages(channel.files.dir);
141
149
  }
142
150
  return messages;
143
151
  };