@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,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveMatrixMonitorAccessState } from "./access-state.js";
3
+
4
+ describe("resolveMatrixMonitorAccessState", () => {
5
+ it("normalizes effective allowlists once and exposes reusable matches", () => {
6
+ const state = resolveMatrixMonitorAccessState({
7
+ allowFrom: ["matrix:@Alice:Example.org"],
8
+ storeAllowFrom: ["user:@bob:example.org"],
9
+ groupAllowFrom: ["@Carol:Example.org"],
10
+ roomUsers: ["user:@Dana:Example.org"],
11
+ senderId: "@dana:example.org",
12
+ isRoom: true,
13
+ });
14
+
15
+ expect(state.effectiveAllowFrom).toEqual([
16
+ "matrix:@alice:example.org",
17
+ "user:@bob:example.org",
18
+ ]);
19
+ expect(state.effectiveGroupAllowFrom).toEqual(["@carol:example.org"]);
20
+ expect(state.effectiveRoomUsers).toEqual(["user:@dana:example.org"]);
21
+ expect(state.directAllowMatch.allowed).toBe(false);
22
+ expect(state.roomUserMatch?.allowed).toBe(true);
23
+ expect(state.groupAllowMatch?.allowed).toBe(false);
24
+ expect(state.commandAuthorizers).toEqual([
25
+ { configured: true, allowed: false },
26
+ { configured: true, allowed: true },
27
+ { configured: true, allowed: false },
28
+ ]);
29
+ });
30
+
31
+ it("keeps room-user matching disabled for dm traffic", () => {
32
+ const state = resolveMatrixMonitorAccessState({
33
+ allowFrom: [],
34
+ storeAllowFrom: [],
35
+ groupAllowFrom: ["@carol:example.org"],
36
+ roomUsers: ["@dana:example.org"],
37
+ senderId: "@dana:example.org",
38
+ isRoom: false,
39
+ });
40
+
41
+ expect(state.roomUserMatch).toBeNull();
42
+ expect(state.commandAuthorizers[1]).toEqual({ configured: true, allowed: false });
43
+ expect(state.commandAuthorizers[2]).toEqual({ configured: true, allowed: false });
44
+ });
45
+ });
@@ -0,0 +1,77 @@
1
+ import { normalizeMatrixAllowList, resolveMatrixAllowListMatch } from "./allowlist.js";
2
+ import type { MatrixAllowListMatch } from "./allowlist.js";
3
+
4
+ type MatrixCommandAuthorizer = {
5
+ configured: boolean;
6
+ allowed: boolean;
7
+ };
8
+
9
+ export type MatrixMonitorAccessState = {
10
+ effectiveAllowFrom: string[];
11
+ effectiveGroupAllowFrom: string[];
12
+ effectiveRoomUsers: string[];
13
+ groupAllowConfigured: boolean;
14
+ directAllowMatch: MatrixAllowListMatch;
15
+ roomUserMatch: MatrixAllowListMatch | null;
16
+ groupAllowMatch: MatrixAllowListMatch | null;
17
+ commandAuthorizers: [MatrixCommandAuthorizer, MatrixCommandAuthorizer, MatrixCommandAuthorizer];
18
+ };
19
+
20
+ export function resolveMatrixMonitorAccessState(params: {
21
+ allowFrom: Array<string | number>;
22
+ storeAllowFrom: Array<string | number>;
23
+ groupAllowFrom: Array<string | number>;
24
+ roomUsers: Array<string | number>;
25
+ senderId: string;
26
+ isRoom: boolean;
27
+ }): MatrixMonitorAccessState {
28
+ const effectiveAllowFrom = normalizeMatrixAllowList([
29
+ ...params.allowFrom,
30
+ ...params.storeAllowFrom,
31
+ ]);
32
+ const effectiveGroupAllowFrom = normalizeMatrixAllowList(params.groupAllowFrom);
33
+ const effectiveRoomUsers = normalizeMatrixAllowList(params.roomUsers);
34
+
35
+ const directAllowMatch = resolveMatrixAllowListMatch({
36
+ allowList: effectiveAllowFrom,
37
+ userId: params.senderId,
38
+ });
39
+ const roomUserMatch =
40
+ params.isRoom && effectiveRoomUsers.length > 0
41
+ ? resolveMatrixAllowListMatch({
42
+ allowList: effectiveRoomUsers,
43
+ userId: params.senderId,
44
+ })
45
+ : null;
46
+ const groupAllowMatch =
47
+ effectiveGroupAllowFrom.length > 0
48
+ ? resolveMatrixAllowListMatch({
49
+ allowList: effectiveGroupAllowFrom,
50
+ userId: params.senderId,
51
+ })
52
+ : null;
53
+
54
+ return {
55
+ effectiveAllowFrom,
56
+ effectiveGroupAllowFrom,
57
+ effectiveRoomUsers,
58
+ groupAllowConfigured: effectiveGroupAllowFrom.length > 0,
59
+ directAllowMatch,
60
+ roomUserMatch,
61
+ groupAllowMatch,
62
+ commandAuthorizers: [
63
+ {
64
+ configured: effectiveAllowFrom.length > 0,
65
+ allowed: directAllowMatch.allowed,
66
+ },
67
+ {
68
+ configured: effectiveRoomUsers.length > 0,
69
+ allowed: roomUserMatch?.allowed ?? false,
70
+ },
71
+ {
72
+ configured: effectiveGroupAllowFrom.length > 0,
73
+ allowed: groupAllowMatch?.allowed ?? false,
74
+ },
75
+ ],
76
+ };
77
+ }
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveMatrixAckReactionConfig } from "./ack-config.js";
3
+
4
+ describe("resolveMatrixAckReactionConfig", () => {
5
+ it("prefers account-level ack reaction and scope overrides", () => {
6
+ expect(
7
+ resolveMatrixAckReactionConfig({
8
+ cfg: {
9
+ messages: {
10
+ ackReaction: "👀",
11
+ ackReactionScope: "all",
12
+ },
13
+ channels: {
14
+ matrix: {
15
+ ackReaction: "✅",
16
+ ackReactionScope: "group-all",
17
+ accounts: {
18
+ ops: {
19
+ ackReaction: "🟢",
20
+ ackReactionScope: "direct",
21
+ },
22
+ },
23
+ },
24
+ },
25
+ },
26
+ agentId: "ops-agent",
27
+ accountId: "ops",
28
+ }),
29
+ ).toEqual({
30
+ ackReaction: "🟢",
31
+ ackReactionScope: "direct",
32
+ });
33
+ });
34
+
35
+ it("falls back to channel then global settings", () => {
36
+ expect(
37
+ resolveMatrixAckReactionConfig({
38
+ cfg: {
39
+ messages: {
40
+ ackReaction: "👀",
41
+ ackReactionScope: "all",
42
+ },
43
+ channels: {
44
+ matrix: {
45
+ ackReaction: "✅",
46
+ },
47
+ },
48
+ },
49
+ agentId: "ops-agent",
50
+ accountId: "missing",
51
+ }),
52
+ ).toEqual({
53
+ ackReaction: "✅",
54
+ ackReactionScope: "all",
55
+ });
56
+ });
57
+ });
@@ -0,0 +1,26 @@
1
+ import type { CoreConfig } from "../../types.js";
2
+ import { resolveMatrixAccountConfig } from "../account-config.js";
3
+ import { resolveAckReaction, type OpenClawConfig } from "./runtime-api.js";
4
+
5
+ type MatrixAckReactionScope = "group-mentions" | "group-all" | "direct" | "all" | "none" | "off";
6
+
7
+ export function resolveMatrixAckReactionConfig(params: {
8
+ cfg: OpenClawConfig;
9
+ agentId: string;
10
+ accountId?: string | null;
11
+ }): { ackReaction: string; ackReactionScope: MatrixAckReactionScope } {
12
+ const matrixConfig = params.cfg.channels?.matrix;
13
+ const accountConfig = resolveMatrixAccountConfig({
14
+ cfg: params.cfg as CoreConfig,
15
+ accountId: params.accountId,
16
+ });
17
+ const ackReaction = resolveAckReaction(params.cfg, params.agentId, {
18
+ channel: "matrix",
19
+ accountId: params.accountId ?? undefined,
20
+ }).trim();
21
+ const ackReactionScope = (accountConfig.ackReactionScope ??
22
+ matrixConfig?.ackReactionScope ??
23
+ params.cfg.messages?.ackReactionScope ??
24
+ "group-mentions") as MatrixAckReactionScope;
25
+ return { ackReaction, ackReactionScope };
26
+ }
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { normalizeMatrixAllowList, resolveMatrixAllowListMatch } from "./allowlist.js";
3
+
4
+ describe("resolveMatrixAllowListMatch", () => {
5
+ it("matches full user IDs and prefixes", () => {
6
+ const userId = "@Alice:Example.org";
7
+ const direct = resolveMatrixAllowListMatch({
8
+ allowList: normalizeMatrixAllowList(["@alice:example.org"]),
9
+ userId,
10
+ });
11
+ expect(direct.allowed).toBe(true);
12
+ expect(direct.matchSource).toBe("id");
13
+
14
+ const prefixedMatrix = resolveMatrixAllowListMatch({
15
+ allowList: normalizeMatrixAllowList(["matrix:@alice:example.org"]),
16
+ userId,
17
+ });
18
+ expect(prefixedMatrix.allowed).toBe(true);
19
+ expect(prefixedMatrix.matchSource).toBe("prefixed-id");
20
+
21
+ const prefixedUser = resolveMatrixAllowListMatch({
22
+ allowList: normalizeMatrixAllowList(["user:@alice:example.org"]),
23
+ userId,
24
+ });
25
+ expect(prefixedUser.allowed).toBe(true);
26
+ expect(prefixedUser.matchSource).toBe("prefixed-user");
27
+ });
28
+
29
+ it("ignores display names and localparts", () => {
30
+ const match = resolveMatrixAllowListMatch({
31
+ allowList: normalizeMatrixAllowList(["alice", "Alice"]),
32
+ userId: "@alice:example.org",
33
+ });
34
+ expect(match.allowed).toBe(false);
35
+ });
36
+
37
+ it("matches wildcard", () => {
38
+ const match = resolveMatrixAllowListMatch({
39
+ allowList: normalizeMatrixAllowList(["*"]),
40
+ userId: "@alice:example.org",
41
+ });
42
+ expect(match.allowed).toBe(true);
43
+ expect(match.matchSource).toBe("wildcard");
44
+ });
45
+ });
@@ -0,0 +1,94 @@
1
+ import {
2
+ resolveAllowlistMatchByCandidates,
3
+ type AllowlistMatch,
4
+ } from "openclaw/plugin-sdk/allow-from";
5
+ import { normalizeStringEntries } from "openclaw/plugin-sdk/string-normalization-runtime";
6
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
7
+
8
+ function normalizeAllowList(list?: Array<string | number>) {
9
+ return normalizeStringEntries(list);
10
+ }
11
+
12
+ function normalizeMatrixUser(raw?: string | null): string {
13
+ const value = (raw ?? "").trim();
14
+ if (!value) {
15
+ return "";
16
+ }
17
+ if (!value.startsWith("@") || !value.includes(":")) {
18
+ return normalizeLowercaseStringOrEmpty(value);
19
+ }
20
+ const withoutAt = value.slice(1);
21
+ const splitIndex = withoutAt.indexOf(":");
22
+ if (splitIndex === -1) {
23
+ return normalizeLowercaseStringOrEmpty(value);
24
+ }
25
+ const localpart = normalizeLowercaseStringOrEmpty(withoutAt.slice(0, splitIndex));
26
+ const server = normalizeLowercaseStringOrEmpty(withoutAt.slice(splitIndex + 1));
27
+ if (!server) {
28
+ return normalizeLowercaseStringOrEmpty(value);
29
+ }
30
+ return `@${localpart}:${server}`;
31
+ }
32
+
33
+ export function normalizeMatrixUserId(raw?: string | null): string {
34
+ const trimmed = (raw ?? "").trim();
35
+ if (!trimmed) {
36
+ return "";
37
+ }
38
+ const lowered = normalizeLowercaseStringOrEmpty(trimmed);
39
+ if (lowered.startsWith("matrix:")) {
40
+ return normalizeMatrixUser(trimmed.slice("matrix:".length));
41
+ }
42
+ if (lowered.startsWith("user:")) {
43
+ return normalizeMatrixUser(trimmed.slice("user:".length));
44
+ }
45
+ return normalizeMatrixUser(trimmed);
46
+ }
47
+
48
+ function normalizeMatrixAllowListEntry(raw: string): string {
49
+ const trimmed = raw.trim();
50
+ if (!trimmed) {
51
+ return "";
52
+ }
53
+ if (trimmed === "*") {
54
+ return trimmed;
55
+ }
56
+ const lowered = normalizeLowercaseStringOrEmpty(trimmed);
57
+ if (lowered.startsWith("matrix:")) {
58
+ return `matrix:${normalizeMatrixUser(trimmed.slice("matrix:".length))}`;
59
+ }
60
+ if (lowered.startsWith("user:")) {
61
+ return `user:${normalizeMatrixUser(trimmed.slice("user:".length))}`;
62
+ }
63
+ return normalizeMatrixUser(trimmed);
64
+ }
65
+
66
+ export function normalizeMatrixAllowList(list?: Array<string | number>) {
67
+ return normalizeAllowList(list).map((entry) => normalizeMatrixAllowListEntry(entry));
68
+ }
69
+
70
+ export type MatrixAllowListMatch = AllowlistMatch<
71
+ "wildcard" | "id" | "prefixed-id" | "prefixed-user"
72
+ >;
73
+
74
+ type MatrixAllowListMatchSource = NonNullable<MatrixAllowListMatch["matchSource"]>;
75
+
76
+ export function resolveMatrixAllowListMatch(params: {
77
+ allowList: string[];
78
+ userId?: string;
79
+ }): MatrixAllowListMatch {
80
+ const allowList = params.allowList;
81
+ if (allowList.length === 0) {
82
+ return { allowed: false };
83
+ }
84
+ if (allowList.includes("*")) {
85
+ return { allowed: true, matchKey: "*", matchSource: "wildcard" };
86
+ }
87
+ const userId = normalizeMatrixUser(params.userId);
88
+ const candidates: Array<{ value?: string; source: MatrixAllowListMatchSource }> = [
89
+ { value: userId, source: "id" },
90
+ { value: userId ? `matrix:${userId}` : "", source: "prefixed-id" },
91
+ { value: userId ? `user:${userId}` : "", source: "prefixed-user" },
92
+ ];
93
+ return resolveAllowlistMatchByCandidates<MatrixAllowListMatchSource>({ allowList, candidates });
94
+ }
@@ -0,0 +1,203 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { PluginRuntime, RuntimeEnv } from "../../../runtime-api.js";
3
+ import { setMatrixRuntime } from "../../runtime.js";
4
+ import type { MatrixConfig } from "../../types.js";
5
+ import { registerMatrixAutoJoin } from "./auto-join.js";
6
+
7
+ type InviteHandler = (roomId: string, inviteEvent: unknown) => Promise<void>;
8
+
9
+ function createClientStub() {
10
+ let inviteHandler: InviteHandler | null = null;
11
+ const client = {
12
+ on: vi.fn((eventName: string, listener: unknown) => {
13
+ if (eventName === "room.invite") {
14
+ inviteHandler = listener as InviteHandler;
15
+ }
16
+ return client;
17
+ }),
18
+ joinRoom: vi.fn(async () => {}),
19
+ resolveRoom: vi.fn(async () => null),
20
+ } as unknown as import("../sdk.js").MatrixClient;
21
+
22
+ return {
23
+ client,
24
+ getInviteHandler: () => inviteHandler,
25
+ joinRoom: (client as unknown as { joinRoom: ReturnType<typeof vi.fn> }).joinRoom,
26
+ resolveRoom: (client as unknown as { resolveRoom: ReturnType<typeof vi.fn> }).resolveRoom,
27
+ };
28
+ }
29
+
30
+ function registerAutoJoinHarness(params: {
31
+ accountConfig?: MatrixConfig;
32
+ resolveRoomValue?: string | null;
33
+ resolveRoomValues?: Array<string | null>;
34
+ error?: ReturnType<typeof vi.fn>;
35
+ }) {
36
+ const harness = createClientStub();
37
+ if (params.resolveRoomValues) {
38
+ for (const value of params.resolveRoomValues) {
39
+ harness.resolveRoom.mockResolvedValueOnce(value);
40
+ }
41
+ } else if (params.resolveRoomValue !== undefined) {
42
+ harness.resolveRoom.mockResolvedValue(params.resolveRoomValue);
43
+ }
44
+
45
+ registerMatrixAutoJoin({
46
+ client: harness.client,
47
+ accountConfig: params.accountConfig ?? {},
48
+ runtime: {
49
+ log: vi.fn(),
50
+ error: params.error ?? vi.fn(),
51
+ } as unknown as RuntimeEnv,
52
+ });
53
+
54
+ return harness;
55
+ }
56
+
57
+ async function triggerInvite(
58
+ getInviteHandler: () => InviteHandler | null,
59
+ inviteEvent: unknown = {},
60
+ ) {
61
+ const inviteHandler = getInviteHandler();
62
+ expect(inviteHandler).toBeTruthy();
63
+ await inviteHandler!("!room:example.org", inviteEvent);
64
+ }
65
+
66
+ describe("registerMatrixAutoJoin", () => {
67
+ beforeEach(() => {
68
+ setMatrixRuntime({
69
+ logging: {
70
+ shouldLogVerbose: () => false,
71
+ },
72
+ } as unknown as PluginRuntime);
73
+ });
74
+
75
+ it("joins all invites when autoJoin=always", async () => {
76
+ const { getInviteHandler, joinRoom } = registerAutoJoinHarness({
77
+ accountConfig: {
78
+ autoJoin: "always",
79
+ },
80
+ });
81
+
82
+ await triggerInvite(getInviteHandler);
83
+ expect(joinRoom).toHaveBeenCalledWith("!room:example.org");
84
+ });
85
+
86
+ it("does not auto-join invites by default", async () => {
87
+ const { getInviteHandler, joinRoom } = registerAutoJoinHarness({});
88
+
89
+ expect(getInviteHandler()).toBeNull();
90
+ expect(joinRoom).not.toHaveBeenCalled();
91
+ });
92
+
93
+ it("ignores invites outside allowlist when autoJoin=allowlist", async () => {
94
+ const { getInviteHandler, joinRoom } = registerAutoJoinHarness({
95
+ accountConfig: {
96
+ autoJoin: "allowlist",
97
+ autoJoinAllowlist: ["#allowed:example.org"],
98
+ },
99
+ resolveRoomValue: null,
100
+ });
101
+
102
+ await triggerInvite(getInviteHandler);
103
+ expect(joinRoom).not.toHaveBeenCalled();
104
+ });
105
+
106
+ it("joins invite when allowlisted alias resolves to the invited room", async () => {
107
+ const { getInviteHandler, joinRoom } = registerAutoJoinHarness({
108
+ accountConfig: {
109
+ autoJoin: "allowlist",
110
+ autoJoinAllowlist: [" #allowed:example.org "],
111
+ },
112
+ resolveRoomValue: "!room:example.org",
113
+ });
114
+
115
+ await triggerInvite(getInviteHandler);
116
+ expect(joinRoom).toHaveBeenCalledWith("!room:example.org");
117
+ });
118
+
119
+ it("retries alias resolution after an unresolved lookup", async () => {
120
+ const { getInviteHandler, joinRoom, resolveRoom } = registerAutoJoinHarness({
121
+ accountConfig: {
122
+ autoJoin: "allowlist",
123
+ autoJoinAllowlist: ["#allowed:example.org"],
124
+ },
125
+ resolveRoomValues: [null, "!room:example.org"],
126
+ });
127
+
128
+ await triggerInvite(getInviteHandler);
129
+ await triggerInvite(getInviteHandler);
130
+
131
+ expect(resolveRoom).toHaveBeenCalledTimes(2);
132
+ expect(joinRoom).toHaveBeenCalledWith("!room:example.org");
133
+ });
134
+
135
+ it("logs and skips allowlist alias resolution failures", async () => {
136
+ const error = vi.fn();
137
+ const { getInviteHandler, joinRoom, resolveRoom } = registerAutoJoinHarness({
138
+ accountConfig: {
139
+ autoJoin: "allowlist",
140
+ autoJoinAllowlist: ["#allowed:example.org"],
141
+ },
142
+ error,
143
+ });
144
+ resolveRoom.mockRejectedValue(new Error("temporary homeserver failure"));
145
+
146
+ const inviteHandler = getInviteHandler();
147
+ expect(inviteHandler).toBeTruthy();
148
+ await expect(inviteHandler!("!room:example.org", {})).resolves.toBeUndefined();
149
+
150
+ expect(joinRoom).not.toHaveBeenCalled();
151
+ expect(error).toHaveBeenCalledWith(
152
+ expect.stringContaining("matrix: failed resolving allowlisted alias #allowed:example.org:"),
153
+ );
154
+ });
155
+
156
+ it("does not trust room-provided alias claims for allowlist joins", async () => {
157
+ const { getInviteHandler, joinRoom } = registerAutoJoinHarness({
158
+ accountConfig: {
159
+ autoJoin: "allowlist",
160
+ autoJoinAllowlist: ["#allowed:example.org"],
161
+ },
162
+ resolveRoomValue: "!different-room:example.org",
163
+ });
164
+
165
+ await triggerInvite(getInviteHandler);
166
+ expect(joinRoom).not.toHaveBeenCalled();
167
+ });
168
+
169
+ it("uses account-scoped auto-join settings for non-default accounts", async () => {
170
+ const { getInviteHandler, joinRoom } = registerAutoJoinHarness({
171
+ accountConfig: {
172
+ autoJoin: "allowlist",
173
+ autoJoinAllowlist: ["#ops-allowed:example.org"],
174
+ },
175
+ resolveRoomValue: "!room:example.org",
176
+ });
177
+
178
+ await triggerInvite(getInviteHandler);
179
+ expect(joinRoom).toHaveBeenCalledWith("!room:example.org");
180
+ });
181
+
182
+ it("joins sender-scoped invites without eager direct repair", async () => {
183
+ const { getInviteHandler, joinRoom } = registerAutoJoinHarness({
184
+ accountConfig: {
185
+ autoJoin: "always",
186
+ },
187
+ });
188
+
189
+ await triggerInvite(getInviteHandler, { sender: "@alice:example.org" });
190
+
191
+ expect(joinRoom).toHaveBeenCalledWith("!room:example.org");
192
+ });
193
+
194
+ it("still joins invites when the sender is unavailable", async () => {
195
+ const { getInviteHandler } = registerAutoJoinHarness({
196
+ accountConfig: {
197
+ autoJoin: "always",
198
+ },
199
+ });
200
+
201
+ await expect(triggerInvite(getInviteHandler, {})).resolves.toBeUndefined();
202
+ });
203
+ });
@@ -0,0 +1,86 @@
1
+ import { normalizeStringifiedOptionalString } from "openclaw/plugin-sdk/text-runtime";
2
+ import { getMatrixRuntime } from "../../runtime.js";
3
+ import type { MatrixConfig } from "../../types.js";
4
+ import type { MatrixClient } from "../sdk.js";
5
+ import type { RuntimeEnv } from "./runtime-api.js";
6
+
7
+ export function registerMatrixAutoJoin(params: {
8
+ client: MatrixClient;
9
+ accountConfig: Pick<MatrixConfig, "autoJoin" | "autoJoinAllowlist">;
10
+ runtime: RuntimeEnv;
11
+ }) {
12
+ const { client, accountConfig, runtime } = params;
13
+ const core = getMatrixRuntime();
14
+ const logVerbose = (message: string) => {
15
+ if (!core.logging.shouldLogVerbose()) {
16
+ return;
17
+ }
18
+ runtime.log?.(message);
19
+ };
20
+ const autoJoin = accountConfig.autoJoin ?? "off";
21
+ const rawAllowlist = (accountConfig.autoJoinAllowlist ?? [])
22
+ .map((entry) => normalizeStringifiedOptionalString(entry))
23
+ .filter((entry): entry is string => Boolean(entry));
24
+ const autoJoinAllowlist = new Set(rawAllowlist);
25
+ const allowedRoomIds = new Set(rawAllowlist.filter((entry) => entry.startsWith("!")));
26
+ const allowedAliases = rawAllowlist.filter((entry) => entry.startsWith("#"));
27
+ const resolvedAliasRoomIds = new Map<string, string>();
28
+
29
+ if (autoJoin === "off") {
30
+ return;
31
+ }
32
+
33
+ if (autoJoin === "always") {
34
+ logVerbose("matrix: auto-join enabled for all invites");
35
+ } else {
36
+ logVerbose("matrix: auto-join enabled for allowlist invites");
37
+ }
38
+
39
+ const resolveAllowedAliasRoomId = async (alias: string): Promise<string | null> => {
40
+ if (resolvedAliasRoomIds.has(alias)) {
41
+ return resolvedAliasRoomIds.get(alias) ?? null;
42
+ }
43
+ const resolved = await params.client.resolveRoom(alias);
44
+ if (resolved) {
45
+ resolvedAliasRoomIds.set(alias, resolved);
46
+ }
47
+ return resolved;
48
+ };
49
+
50
+ const resolveAllowedAliasRoomIds = async (): Promise<string[]> => {
51
+ const resolved = await Promise.all(
52
+ allowedAliases.map(async (alias) => {
53
+ try {
54
+ return await resolveAllowedAliasRoomId(alias);
55
+ } catch (err) {
56
+ runtime.error?.(`matrix: failed resolving allowlisted alias ${alias}: ${String(err)}`);
57
+ return null;
58
+ }
59
+ }),
60
+ );
61
+ return resolved.filter((roomId): roomId is string => Boolean(roomId));
62
+ };
63
+
64
+ // Handle invites directly so both "always" and "allowlist" modes share the same path.
65
+ client.on("room.invite", async (roomId: string, _inviteEvent: unknown) => {
66
+ if (autoJoin === "allowlist") {
67
+ const allowedAliasRoomIds = await resolveAllowedAliasRoomIds();
68
+ const allowed =
69
+ autoJoinAllowlist.has("*") ||
70
+ allowedRoomIds.has(roomId) ||
71
+ allowedAliasRoomIds.some((resolvedRoomId) => resolvedRoomId === roomId);
72
+
73
+ if (!allowed) {
74
+ logVerbose(`matrix: invite ignored (not in allowlist) room=${roomId}`);
75
+ return;
76
+ }
77
+ }
78
+
79
+ try {
80
+ await client.joinRoom(roomId);
81
+ logVerbose(`matrix: joined room ${roomId}`);
82
+ } catch (err) {
83
+ runtime.error?.(`matrix: failed to join room ${roomId}: ${String(err)}`);
84
+ }
85
+ });
86
+ }