@archipelagolab/lobi 1.0.0

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 (315) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/ENDOFFILE +0 -0
  3. package/EOF +0 -0
  4. package/LICENSE +21 -0
  5. package/SPEC-SUPPORT.md +116 -0
  6. package/YAMLEND +0 -0
  7. package/api.ts +18 -0
  8. package/archipelagolab-lobi-1.0.0.tgz +0 -0
  9. package/auth-presence.ts +56 -0
  10. package/channel-plugin-api.ts +3 -0
  11. package/cli-metadata.ts +11 -0
  12. package/contract-api.ts +17 -0
  13. package/docs/CHECKLIST.md +83 -0
  14. package/docs/FORK_SDK_GUIDE.md +279 -0
  15. package/helper-api.ts +3 -0
  16. package/index.test.ts +61 -0
  17. package/index.ts +65 -0
  18. package/openclaw.plugin.json +23 -0
  19. package/package.json +52 -0
  20. package/plugin-entry.handlers.runtime.ts +1 -0
  21. package/runtime-api.ts +54 -0
  22. package/runtime-heavy-api.ts +1 -0
  23. package/scripts/migrate-to-lobi.sh +72 -0
  24. package/secret-contract-api.ts +5 -0
  25. package/setup-entry.ts +13 -0
  26. package/src/account-selection.test.ts +124 -0
  27. package/src/account-selection.ts +226 -0
  28. package/src/actions.account-propagation.test.ts +251 -0
  29. package/src/actions.test.ts +251 -0
  30. package/src/actions.ts +336 -0
  31. package/src/approval-auth.test.ts +23 -0
  32. package/src/approval-auth.ts +25 -0
  33. package/src/approval-handler.runtime.test.ts +46 -0
  34. package/src/approval-handler.runtime.ts +400 -0
  35. package/src/approval-ids.ts +6 -0
  36. package/src/approval-native.test.ts +329 -0
  37. package/src/approval-native.ts +336 -0
  38. package/src/approval-reactions.test.ts +107 -0
  39. package/src/approval-reactions.ts +158 -0
  40. package/src/auth-precedence.ts +61 -0
  41. package/src/channel-account-paths.ts +92 -0
  42. package/src/channel.account-paths.test.ts +102 -0
  43. package/src/channel.directory.test.ts +601 -0
  44. package/src/channel.resolve.test.ts +38 -0
  45. package/src/channel.runtime.ts +16 -0
  46. package/src/channel.setup.test.ts +269 -0
  47. package/src/channel.ts +570 -0
  48. package/src/cli-metadata.ts +19 -0
  49. package/src/cli.test.ts +1015 -0
  50. package/src/cli.ts +1198 -0
  51. package/src/config-adapter.ts +41 -0
  52. package/src/config-schema.test.ts +90 -0
  53. package/src/config-schema.ts +114 -0
  54. package/src/directory-live.test.ts +200 -0
  55. package/src/directory-live.ts +238 -0
  56. package/src/doctor-contract.ts +287 -0
  57. package/src/doctor.test.ts +440 -0
  58. package/src/doctor.ts +262 -0
  59. package/src/env-vars.ts +92 -0
  60. package/src/exec-approval-resolver.test.ts +68 -0
  61. package/src/exec-approval-resolver.ts +23 -0
  62. package/src/exec-approvals.test.ts +483 -0
  63. package/src/exec-approvals.ts +290 -0
  64. package/src/group-mentions.ts +41 -0
  65. package/src/legacy-crypto-inspector-availability.test.ts +81 -0
  66. package/src/legacy-crypto-inspector-availability.ts +60 -0
  67. package/src/legacy-crypto.test.ts +234 -0
  68. package/src/legacy-crypto.ts +549 -0
  69. package/src/legacy-state.test.ts +86 -0
  70. package/src/legacy-state.ts +156 -0
  71. package/src/matrix/account-config.ts +150 -0
  72. package/src/matrix/accounts.readiness.test.ts +27 -0
  73. package/src/matrix/accounts.test.ts +757 -0
  74. package/src/matrix/accounts.ts +194 -0
  75. package/src/matrix/actions/client.test.ts +215 -0
  76. package/src/matrix/actions/client.ts +31 -0
  77. package/src/matrix/actions/devices.test.ts +114 -0
  78. package/src/matrix/actions/devices.ts +34 -0
  79. package/src/matrix/actions/limits.test.ts +15 -0
  80. package/src/matrix/actions/limits.ts +6 -0
  81. package/src/matrix/actions/messages.test.ts +289 -0
  82. package/src/matrix/actions/messages.ts +123 -0
  83. package/src/matrix/actions/pins.test.ts +74 -0
  84. package/src/matrix/actions/pins.ts +64 -0
  85. package/src/matrix/actions/polls.test.ts +71 -0
  86. package/src/matrix/actions/polls.ts +109 -0
  87. package/src/matrix/actions/profile.test.ts +109 -0
  88. package/src/matrix/actions/profile.ts +37 -0
  89. package/src/matrix/actions/reactions.test.ts +135 -0
  90. package/src/matrix/actions/reactions.ts +59 -0
  91. package/src/matrix/actions/room.test.ts +79 -0
  92. package/src/matrix/actions/room.ts +71 -0
  93. package/src/matrix/actions/summary.test.ts +87 -0
  94. package/src/matrix/actions/summary.ts +88 -0
  95. package/src/matrix/actions/types.ts +82 -0
  96. package/src/matrix/actions/verification.test.ts +105 -0
  97. package/src/matrix/actions/verification.ts +237 -0
  98. package/src/matrix/actions.ts +37 -0
  99. package/src/matrix/active-client.ts +26 -0
  100. package/src/matrix/async-lock.ts +18 -0
  101. package/src/matrix/backup-health.ts +115 -0
  102. package/src/matrix/client/config-runtime-api.ts +14 -0
  103. package/src/matrix/client/config-secret-input.runtime.ts +1 -0
  104. package/src/matrix/client/config.ts +982 -0
  105. package/src/matrix/client/create-client.test.ts +115 -0
  106. package/src/matrix/client/create-client.ts +101 -0
  107. package/src/matrix/client/env-auth.ts +6 -0
  108. package/src/matrix/client/file-sync-store.test.ts +265 -0
  109. package/src/matrix/client/file-sync-store.ts +289 -0
  110. package/src/matrix/client/logging.ts +123 -0
  111. package/src/matrix/client/migration-snapshot.runtime.ts +1 -0
  112. package/src/matrix/client/private-network-host.ts +56 -0
  113. package/src/matrix/client/runtime.ts +4 -0
  114. package/src/matrix/client/shared.test.ts +344 -0
  115. package/src/matrix/client/shared.ts +306 -0
  116. package/src/matrix/client/storage.test.ts +634 -0
  117. package/src/matrix/client/storage.ts +544 -0
  118. package/src/matrix/client/types.ts +50 -0
  119. package/src/matrix/client-bootstrap.test.ts +84 -0
  120. package/src/matrix/client-bootstrap.ts +164 -0
  121. package/src/matrix/client-resolver.test-helpers.ts +147 -0
  122. package/src/matrix/client.test.ts +1521 -0
  123. package/src/matrix/client.ts +23 -0
  124. package/src/matrix/config-paths.ts +31 -0
  125. package/src/matrix/config-update.test.ts +237 -0
  126. package/src/matrix/config-update.ts +291 -0
  127. package/src/matrix/credentials-read.ts +206 -0
  128. package/src/matrix/credentials-write.runtime.ts +26 -0
  129. package/src/matrix/credentials.test.ts +501 -0
  130. package/src/matrix/credentials.ts +95 -0
  131. package/src/matrix/deps.test.ts +74 -0
  132. package/src/matrix/deps.ts +225 -0
  133. package/src/matrix/device-health.test.ts +45 -0
  134. package/src/matrix/device-health.ts +31 -0
  135. package/src/matrix/direct-management.test.ts +350 -0
  136. package/src/matrix/direct-management.ts +347 -0
  137. package/src/matrix/direct-room.test.ts +61 -0
  138. package/src/matrix/direct-room.ts +128 -0
  139. package/src/matrix/draft-stream.test.ts +406 -0
  140. package/src/matrix/draft-stream.ts +216 -0
  141. package/src/matrix/encryption-guidance.ts +27 -0
  142. package/src/matrix/errors.ts +21 -0
  143. package/src/matrix/format.test.ts +340 -0
  144. package/src/matrix/format.ts +428 -0
  145. package/src/matrix/legacy-crypto-inspector.ts +95 -0
  146. package/src/matrix/media-errors.ts +20 -0
  147. package/src/matrix/media-text.ts +169 -0
  148. package/src/matrix/monitor/access-state.test.ts +45 -0
  149. package/src/matrix/monitor/access-state.ts +77 -0
  150. package/src/matrix/monitor/ack-config.test.ts +57 -0
  151. package/src/matrix/monitor/ack-config.ts +26 -0
  152. package/src/matrix/monitor/allowlist.test.ts +45 -0
  153. package/src/matrix/monitor/allowlist.ts +94 -0
  154. package/src/matrix/monitor/auto-join.test.ts +203 -0
  155. package/src/matrix/monitor/auto-join.ts +86 -0
  156. package/src/matrix/monitor/config.test.ts +197 -0
  157. package/src/matrix/monitor/config.ts +303 -0
  158. package/src/matrix/monitor/context-summary.ts +43 -0
  159. package/src/matrix/monitor/direct.test.ts +529 -0
  160. package/src/matrix/monitor/direct.ts +270 -0
  161. package/src/matrix/monitor/events.test.ts +1524 -0
  162. package/src/matrix/monitor/events.ts +213 -0
  163. package/src/matrix/monitor/handler.body-for-agent.test.ts +396 -0
  164. package/src/matrix/monitor/handler.group-history.test.ts +648 -0
  165. package/src/matrix/monitor/handler.media-failure.test.ts +267 -0
  166. package/src/matrix/monitor/handler.test-helpers.ts +308 -0
  167. package/src/matrix/monitor/handler.test.ts +2952 -0
  168. package/src/matrix/monitor/handler.thread-root-media.test.ts +82 -0
  169. package/src/matrix/monitor/handler.ts +1679 -0
  170. package/src/matrix/monitor/inbound-dedupe.test.ts +146 -0
  171. package/src/matrix/monitor/inbound-dedupe.ts +267 -0
  172. package/src/matrix/monitor/index.test.ts +920 -0
  173. package/src/matrix/monitor/index.ts +434 -0
  174. package/src/matrix/monitor/legacy-crypto-restore.test.ts +206 -0
  175. package/src/matrix/monitor/legacy-crypto-restore.ts +139 -0
  176. package/src/matrix/monitor/location.ts +100 -0
  177. package/src/matrix/monitor/media.test.ts +159 -0
  178. package/src/matrix/monitor/media.ts +119 -0
  179. package/src/matrix/monitor/mentions.test.ts +289 -0
  180. package/src/matrix/monitor/mentions.ts +177 -0
  181. package/src/matrix/monitor/reaction-events.test.ts +326 -0
  182. package/src/matrix/monitor/reaction-events.ts +187 -0
  183. package/src/matrix/monitor/recent-invite.test.ts +92 -0
  184. package/src/matrix/monitor/recent-invite.ts +30 -0
  185. package/src/matrix/monitor/replies.test.ts +265 -0
  186. package/src/matrix/monitor/replies.ts +136 -0
  187. package/src/matrix/monitor/reply-context.test.ts +276 -0
  188. package/src/matrix/monitor/reply-context.ts +92 -0
  189. package/src/matrix/monitor/room-history.test.ts +258 -0
  190. package/src/matrix/monitor/room-history.ts +301 -0
  191. package/src/matrix/monitor/room-info.test.ts +201 -0
  192. package/src/matrix/monitor/room-info.ts +126 -0
  193. package/src/matrix/monitor/rooms.test.ts +121 -0
  194. package/src/matrix/monitor/rooms.ts +52 -0
  195. package/src/matrix/monitor/route.test.ts +255 -0
  196. package/src/matrix/monitor/route.ts +178 -0
  197. package/src/matrix/monitor/runtime-api.ts +31 -0
  198. package/src/matrix/monitor/startup-verification.test.ts +294 -0
  199. package/src/matrix/monitor/startup-verification.ts +237 -0
  200. package/src/matrix/monitor/startup.test.ts +257 -0
  201. package/src/matrix/monitor/startup.ts +218 -0
  202. package/src/matrix/monitor/status.ts +111 -0
  203. package/src/matrix/monitor/sync-lifecycle.test.ts +224 -0
  204. package/src/matrix/monitor/sync-lifecycle.ts +91 -0
  205. package/src/matrix/monitor/task-runner.ts +38 -0
  206. package/src/matrix/monitor/thread-context.test.ts +149 -0
  207. package/src/matrix/monitor/thread-context.ts +108 -0
  208. package/src/matrix/monitor/threads.test.ts +68 -0
  209. package/src/matrix/monitor/threads.ts +85 -0
  210. package/src/matrix/monitor/types.ts +30 -0
  211. package/src/matrix/monitor/verification-events.ts +627 -0
  212. package/src/matrix/monitor/verification-utils.test.ts +47 -0
  213. package/src/matrix/monitor/verification-utils.ts +46 -0
  214. package/src/matrix/outbound-media-runtime.ts +1 -0
  215. package/src/matrix/poll-summary.ts +110 -0
  216. package/src/matrix/poll-types.test.ts +205 -0
  217. package/src/matrix/poll-types.ts +433 -0
  218. package/src/matrix/probe.runtime.ts +4 -0
  219. package/src/matrix/probe.test.ts +154 -0
  220. package/src/matrix/probe.ts +96 -0
  221. package/src/matrix/profile.test.ts +154 -0
  222. package/src/matrix/profile.ts +184 -0
  223. package/src/matrix/reaction-common.test.ts +96 -0
  224. package/src/matrix/reaction-common.ts +147 -0
  225. package/src/matrix/sdk/crypto-bootstrap.test.ts +505 -0
  226. package/src/matrix/sdk/crypto-bootstrap.ts +341 -0
  227. package/src/matrix/sdk/crypto-facade.test.ts +197 -0
  228. package/src/matrix/sdk/crypto-facade.ts +207 -0
  229. package/src/matrix/sdk/crypto-node.runtime.test.ts +27 -0
  230. package/src/matrix/sdk/crypto-node.runtime.ts +9 -0
  231. package/src/matrix/sdk/crypto-runtime.ts +11 -0
  232. package/src/matrix/sdk/decrypt-bridge.ts +356 -0
  233. package/src/matrix/sdk/event-helpers.test.ts +60 -0
  234. package/src/matrix/sdk/event-helpers.ts +71 -0
  235. package/src/matrix/sdk/http-client.test.ts +134 -0
  236. package/src/matrix/sdk/http-client.ts +87 -0
  237. package/src/matrix/sdk/idb-persistence-lock.ts +51 -0
  238. package/src/matrix/sdk/idb-persistence.lock-order.test.ts +108 -0
  239. package/src/matrix/sdk/idb-persistence.test-helpers.ts +88 -0
  240. package/src/matrix/sdk/idb-persistence.test.ts +149 -0
  241. package/src/matrix/sdk/idb-persistence.ts +283 -0
  242. package/src/matrix/sdk/logger.test.ts +25 -0
  243. package/src/matrix/sdk/logger.ts +108 -0
  244. package/src/matrix/sdk/read-response-with-limit.ts +19 -0
  245. package/src/matrix/sdk/recovery-key-store.test.ts +385 -0
  246. package/src/matrix/sdk/recovery-key-store.ts +430 -0
  247. package/src/matrix/sdk/transport.test.ts +161 -0
  248. package/src/matrix/sdk/transport.ts +344 -0
  249. package/src/matrix/sdk/types.ts +236 -0
  250. package/src/matrix/sdk/verification-manager.test.ts +509 -0
  251. package/src/matrix/sdk/verification-manager.ts +694 -0
  252. package/src/matrix/sdk/verification-status.ts +23 -0
  253. package/src/matrix/sdk.test.ts +2568 -0
  254. package/src/matrix/sdk.ts +1789 -0
  255. package/src/matrix/send/client.test.ts +174 -0
  256. package/src/matrix/send/client.ts +90 -0
  257. package/src/matrix/send/formatting.ts +189 -0
  258. package/src/matrix/send/media.ts +244 -0
  259. package/src/matrix/send/targets.test.ts +254 -0
  260. package/src/matrix/send/targets.ts +104 -0
  261. package/src/matrix/send/types.ts +134 -0
  262. package/src/matrix/send.test.ts +958 -0
  263. package/src/matrix/send.ts +609 -0
  264. package/src/matrix/session-store-metadata.ts +108 -0
  265. package/src/matrix/startup-abort.ts +44 -0
  266. package/src/matrix/sync-state.ts +27 -0
  267. package/src/matrix/target-ids.ts +102 -0
  268. package/src/matrix/thread-bindings-shared.ts +201 -0
  269. package/src/matrix/thread-bindings.test.ts +673 -0
  270. package/src/matrix/thread-bindings.ts +577 -0
  271. package/src/matrix-migration.runtime.ts +9 -0
  272. package/src/migration-config.test.ts +228 -0
  273. package/src/migration-config.ts +243 -0
  274. package/src/migration-snapshot-backup.ts +117 -0
  275. package/src/migration-snapshot.test.ts +184 -0
  276. package/src/migration-snapshot.ts +55 -0
  277. package/src/onboarding.resolve.test.ts +55 -0
  278. package/src/onboarding.test-harness.ts +158 -0
  279. package/src/onboarding.test.ts +665 -0
  280. package/src/onboarding.ts +773 -0
  281. package/src/outbound.test.ts +173 -0
  282. package/src/outbound.ts +78 -0
  283. package/src/plugin-entry.runtime.js +159 -0
  284. package/src/plugin-entry.runtime.test.ts +108 -0
  285. package/src/plugin-entry.runtime.ts +68 -0
  286. package/src/profile-update.ts +68 -0
  287. package/src/record-shared.ts +3 -0
  288. package/src/resolve-targets.test.ts +178 -0
  289. package/src/resolve-targets.ts +175 -0
  290. package/src/resolver.ts +21 -0
  291. package/src/runtime-api.ts +144 -0
  292. package/src/runtime.ts +7 -0
  293. package/src/secret-contract.ts +174 -0
  294. package/src/session-route.test.ts +315 -0
  295. package/src/session-route.ts +113 -0
  296. package/src/setup-bootstrap.ts +94 -0
  297. package/src/setup-config.ts +222 -0
  298. package/src/setup-contract.ts +89 -0
  299. package/src/setup-core.test.ts +326 -0
  300. package/src/setup-core.ts +50 -0
  301. package/src/setup-surface.ts +4 -0
  302. package/src/startup-maintenance.test.ts +227 -0
  303. package/src/startup-maintenance.ts +114 -0
  304. package/src/storage-paths.ts +92 -0
  305. package/src/test-helpers.ts +42 -0
  306. package/src/test-mocks.ts +55 -0
  307. package/src/test-runtime.ts +72 -0
  308. package/src/test-support/monitor-route-test-support.ts +8 -0
  309. package/src/tool-actions.runtime.ts +1 -0
  310. package/src/tool-actions.test.ts +422 -0
  311. package/src/tool-actions.ts +498 -0
  312. package/src/types.ts +230 -0
  313. package/test-api.ts +2 -0
  314. package/thread-bindings-runtime.ts +4 -0
  315. package/tsconfig.json +16 -0
@@ -0,0 +1,270 @@
1
+ import { promoteMatrixDirectRoomCandidate } from "../direct-management.js";
2
+ import {
3
+ hasDirectMatrixMemberFlag,
4
+ isStrictDirectMembership,
5
+ readJoinedMatrixMembers,
6
+ } from "../direct-room.js";
7
+ import type { MatrixClient } from "../sdk.js";
8
+
9
+ type DirectMessageCheck = {
10
+ roomId: string;
11
+ senderId?: string;
12
+ selfUserId?: string;
13
+ };
14
+
15
+ type DirectRoomTrackerOptions = {
16
+ log?: (message: string) => void;
17
+ canPromoteRecentInvite?: (roomId: string) => boolean | Promise<boolean>;
18
+ shouldKeepLocallyPromotedDirectRoom?:
19
+ | ((roomId: string) => boolean | undefined | Promise<boolean | undefined>)
20
+ | undefined;
21
+ };
22
+
23
+ const DM_CACHE_TTL_MS = 30_000;
24
+ const RECENT_INVITE_TTL_MS = 30_000;
25
+ const MAX_TRACKED_DM_ROOMS = 1024;
26
+ const MAX_TRACKED_DM_MEMBER_FLAGS = 2048;
27
+
28
+ function rememberBounded<T>(
29
+ map: Map<string, T>,
30
+ key: string,
31
+ value: T,
32
+ maxSize = MAX_TRACKED_DM_ROOMS,
33
+ ): void {
34
+ map.set(key, value);
35
+ if (map.size > maxSize) {
36
+ const oldest = map.keys().next().value;
37
+ if (typeof oldest === "string") {
38
+ map.delete(oldest);
39
+ }
40
+ }
41
+ }
42
+
43
+ export function createDirectRoomTracker(client: MatrixClient, opts: DirectRoomTrackerOptions = {}) {
44
+ const log = opts.log ?? (() => {});
45
+ let lastDmUpdateMs = 0;
46
+ // Once m.direct has seeded successfully, prefer the explicit cache over
47
+ // re-enabling the broad 2-person fallback after a later transient failure.
48
+ let hasSeededDmCache = false;
49
+ let cachedSelfUserId: string | null = null;
50
+ const joinedMembersCache = new Map<string, { members: string[]; ts: number }>();
51
+ const directMemberFlagCache = new Map<string, { isDirect: boolean | null; ts: number }>();
52
+ const recentInviteCandidates = new Map<string, { remoteUserId: string; ts: number }>();
53
+ const locallyPromotedDirectRooms = new Map<string, { remoteUserId: string }>();
54
+
55
+ const ensureSelfUserId = async (): Promise<string | null> => {
56
+ if (cachedSelfUserId) {
57
+ return cachedSelfUserId;
58
+ }
59
+ try {
60
+ cachedSelfUserId = await client.getUserId();
61
+ } catch {
62
+ cachedSelfUserId = null;
63
+ }
64
+ return cachedSelfUserId;
65
+ };
66
+
67
+ const refreshDmCache = async (): Promise<void> => {
68
+ const now = Date.now();
69
+ if (now - lastDmUpdateMs < DM_CACHE_TTL_MS) {
70
+ return;
71
+ }
72
+ lastDmUpdateMs = now;
73
+ hasSeededDmCache = (await client.dms.update()) || hasSeededDmCache;
74
+ };
75
+
76
+ const resolveJoinedMembers = async (roomId: string): Promise<string[] | null> => {
77
+ const cached = joinedMembersCache.get(roomId);
78
+ const now = Date.now();
79
+ if (cached && now - cached.ts < DM_CACHE_TTL_MS) {
80
+ return cached.members;
81
+ }
82
+ try {
83
+ const normalized = await readJoinedMatrixMembers(client, roomId);
84
+ if (!normalized) {
85
+ throw new Error("membership unavailable");
86
+ }
87
+ rememberBounded(joinedMembersCache, roomId, { members: normalized, ts: now });
88
+ return normalized;
89
+ } catch (err) {
90
+ log(`matrix: dm member lookup failed room=${roomId} (${String(err)})`);
91
+ return null;
92
+ }
93
+ };
94
+
95
+ const resolveDirectMemberFlag = async (
96
+ roomId: string,
97
+ userId?: string | null,
98
+ ): Promise<boolean | null> => {
99
+ const normalizedUserId = userId?.trim();
100
+ if (!normalizedUserId) {
101
+ return null;
102
+ }
103
+ const cacheKey = `${roomId}\n${normalizedUserId}`;
104
+ const cached = directMemberFlagCache.get(cacheKey);
105
+ const now = Date.now();
106
+ if (cached && now - cached.ts < DM_CACHE_TTL_MS) {
107
+ return cached.isDirect;
108
+ }
109
+ const isDirect = await hasDirectMatrixMemberFlag(client, roomId, normalizedUserId);
110
+ rememberBounded(
111
+ directMemberFlagCache,
112
+ cacheKey,
113
+ { isDirect, ts: now },
114
+ MAX_TRACKED_DM_MEMBER_FLAGS,
115
+ );
116
+ return isDirect;
117
+ };
118
+
119
+ const hasRecentInviteCandidate = (roomId: string, remoteUserId?: string | null): boolean => {
120
+ const normalizedRemoteUserId = remoteUserId?.trim();
121
+ if (!normalizedRemoteUserId) {
122
+ return false;
123
+ }
124
+ const cached = recentInviteCandidates.get(roomId);
125
+ if (!cached) {
126
+ return false;
127
+ }
128
+ if (Date.now() - cached.ts >= RECENT_INVITE_TTL_MS) {
129
+ recentInviteCandidates.delete(roomId);
130
+ return false;
131
+ }
132
+ return cached.remoteUserId === normalizedRemoteUserId;
133
+ };
134
+
135
+ const canPromoteRecentInvite = async (roomId: string): Promise<boolean> => {
136
+ try {
137
+ return (await opts.canPromoteRecentInvite?.(roomId)) ?? true;
138
+ } catch (err) {
139
+ log(`matrix: recent invite promotion veto failed room=${roomId} (${String(err)})`);
140
+ return false;
141
+ }
142
+ };
143
+
144
+ const shouldKeepLocallyPromotedDirectRoom = async (
145
+ roomId: string,
146
+ ): Promise<boolean | undefined> => {
147
+ try {
148
+ return await opts.shouldKeepLocallyPromotedDirectRoom?.(roomId);
149
+ } catch (err) {
150
+ log(`matrix: local promotion keep-check failed room=${roomId} (${String(err)})`);
151
+ return undefined;
152
+ }
153
+ };
154
+
155
+ const hasLocallyPromotedDirectRoom = (roomId: string, remoteUserId?: string | null): boolean => {
156
+ const normalizedRemoteUserId = remoteUserId?.trim();
157
+ if (!normalizedRemoteUserId) {
158
+ return false;
159
+ }
160
+ return locallyPromotedDirectRooms.get(roomId)?.remoteUserId === normalizedRemoteUserId;
161
+ };
162
+
163
+ const rememberLocallyPromotedDirectRoom = (roomId: string, remoteUserId: string): void => {
164
+ const normalizedRemoteUserId = remoteUserId.trim();
165
+ if (!normalizedRemoteUserId) {
166
+ return;
167
+ }
168
+ rememberBounded(locallyPromotedDirectRooms, roomId, {
169
+ remoteUserId: normalizedRemoteUserId,
170
+ });
171
+ };
172
+
173
+ return {
174
+ invalidateRoom: (roomId: string): void => {
175
+ joinedMembersCache.delete(roomId);
176
+ for (const key of directMemberFlagCache.keys()) {
177
+ if (key.startsWith(`${roomId}\n`)) {
178
+ directMemberFlagCache.delete(key);
179
+ }
180
+ }
181
+ lastDmUpdateMs = 0;
182
+ log(`matrix: invalidated dm cache room=${roomId}`);
183
+ },
184
+ rememberInvite: (roomId: string, remoteUserId: string): void => {
185
+ const normalizedRemoteUserId = remoteUserId.trim();
186
+ if (!normalizedRemoteUserId) {
187
+ return;
188
+ }
189
+ rememberBounded(recentInviteCandidates, roomId, {
190
+ remoteUserId: normalizedRemoteUserId,
191
+ ts: Date.now(),
192
+ });
193
+ log(`matrix: remembered invite candidate room=${roomId} sender=${normalizedRemoteUserId}`);
194
+ },
195
+ isDirectMessage: async (params: DirectMessageCheck): Promise<boolean> => {
196
+ const { roomId, senderId } = params;
197
+ const selfUserId = params.selfUserId ?? (await ensureSelfUserId());
198
+ const joinedMembers = await resolveJoinedMembers(roomId);
199
+ const strictDirectMembership = isStrictDirectMembership({
200
+ selfUserId,
201
+ remoteUserId: senderId,
202
+ joinedMembers,
203
+ });
204
+
205
+ try {
206
+ await refreshDmCache();
207
+ } catch (err) {
208
+ log(`matrix: dm cache refresh failed (${String(err)})`);
209
+ }
210
+
211
+ if (client.dms.isDm(roomId)) {
212
+ if (strictDirectMembership) {
213
+ log(`matrix: dm detected via m.direct room=${roomId}`);
214
+ return true;
215
+ }
216
+ log(`matrix: ignoring stale m.direct classification room=${roomId}`);
217
+ }
218
+
219
+ if (strictDirectMembership) {
220
+ const directViaSelf = await resolveDirectMemberFlag(roomId, selfUserId);
221
+ if (directViaSelf === true) {
222
+ log(`matrix: dm detected via member state room=${roomId}`);
223
+ return true;
224
+ }
225
+ if (directViaSelf === false) {
226
+ log(`matrix: dm rejected via member state room=${roomId}`);
227
+ return false;
228
+ }
229
+
230
+ if (!hasSeededDmCache) {
231
+ log(
232
+ `matrix: dm detected via exact 2-member fallback before dm cache seed room=${roomId}`,
233
+ );
234
+ return true;
235
+ }
236
+
237
+ if (hasLocallyPromotedDirectRoom(roomId, senderId)) {
238
+ const shouldKeep = await shouldKeepLocallyPromotedDirectRoom(roomId);
239
+ if (shouldKeep !== false) {
240
+ log(`matrix: dm detected via local promotion room=${roomId}`);
241
+ return true;
242
+ }
243
+ locallyPromotedDirectRooms.delete(roomId);
244
+ log(`matrix: local promotion cleared room=${roomId}`);
245
+ }
246
+
247
+ if (hasRecentInviteCandidate(roomId, senderId) && (await canPromoteRecentInvite(roomId))) {
248
+ const promotion = await promoteMatrixDirectRoomCandidate({
249
+ client,
250
+ remoteUserId: senderId ?? "",
251
+ roomId,
252
+ selfUserId,
253
+ });
254
+ if (promotion.classifyAsDirect) {
255
+ rememberLocallyPromotedDirectRoom(roomId, senderId ?? "");
256
+ log(
257
+ `matrix: dm detected via recent invite room=${roomId} reason=${promotion.reason} repaired=${String(promotion.repaired)}`,
258
+ );
259
+ return true;
260
+ }
261
+ }
262
+ }
263
+
264
+ log(
265
+ `matrix: dm check room=${roomId} result=group members=${joinedMembers?.length ?? "unknown"}`,
266
+ );
267
+ return false;
268
+ },
269
+ };
270
+ }