@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,601 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
3
+ import type { RuntimeEnv } from "../runtime-api.js";
4
+ import { matrixPlugin } from "./channel.js";
5
+ import { resolveMatrixAccount } from "./matrix/accounts.js";
6
+ import { resolveMatrixConfigForAccount } from "./matrix/client/config.js";
7
+ import { installMatrixTestRuntime } from "./test-runtime.js";
8
+ import type { CoreConfig } from "./types.js";
9
+
10
+ describe("matrix directory", () => {
11
+ const runtimeEnv: RuntimeEnv = createRuntimeEnv();
12
+
13
+ beforeEach(() => {
14
+ installMatrixTestRuntime();
15
+ });
16
+
17
+ it("lists peers and groups from config", async () => {
18
+ const cfg = {
19
+ channels: {
20
+ matrix: {
21
+ dm: { allowFrom: ["matrix:@alice:example.org", "bob"] },
22
+ groupAllowFrom: ["@dana:example.org"],
23
+ groups: {
24
+ "!room1:example.org": { users: ["@carol:example.org"] },
25
+ "#alias:example.org": { users: [] },
26
+ },
27
+ },
28
+ },
29
+ } as unknown as CoreConfig;
30
+
31
+ expect(matrixPlugin.directory).toBeTruthy();
32
+ expect(matrixPlugin.directory?.listPeers).toBeTruthy();
33
+ expect(matrixPlugin.directory?.listGroups).toBeTruthy();
34
+
35
+ await expect(
36
+ matrixPlugin.directory!.listPeers!({
37
+ cfg,
38
+ accountId: undefined,
39
+ query: undefined,
40
+ limit: undefined,
41
+ runtime: runtimeEnv,
42
+ }),
43
+ ).resolves.toEqual(
44
+ expect.arrayContaining([
45
+ { kind: "user", id: "user:@alice:example.org" },
46
+ { kind: "user", id: "bob", name: "incomplete id; expected @user:server" },
47
+ { kind: "user", id: "user:@carol:example.org" },
48
+ { kind: "user", id: "user:@dana:example.org" },
49
+ ]),
50
+ );
51
+
52
+ await expect(
53
+ matrixPlugin.directory!.listGroups!({
54
+ cfg,
55
+ accountId: undefined,
56
+ query: undefined,
57
+ limit: undefined,
58
+ runtime: runtimeEnv,
59
+ }),
60
+ ).resolves.toEqual(
61
+ expect.arrayContaining([
62
+ { kind: "group", id: "room:!room1:example.org" },
63
+ { kind: "group", id: "#alias:example.org" },
64
+ ]),
65
+ );
66
+ });
67
+
68
+ it("resolves replyToMode from account config", () => {
69
+ const cfg = {
70
+ channels: {
71
+ matrix: {
72
+ replyToMode: "off",
73
+ accounts: {
74
+ Assistant: {
75
+ replyToMode: "all",
76
+ },
77
+ },
78
+ },
79
+ },
80
+ } as unknown as CoreConfig;
81
+
82
+ expect(matrixPlugin.threading?.resolveReplyToMode).toBeTruthy();
83
+ expect(
84
+ matrixPlugin.threading?.resolveReplyToMode?.({
85
+ cfg,
86
+ accountId: "assistant",
87
+ chatType: "direct",
88
+ }),
89
+ ).toBe("all");
90
+ expect(
91
+ matrixPlugin.threading?.resolveReplyToMode?.({
92
+ cfg,
93
+ accountId: "default",
94
+ chatType: "direct",
95
+ }),
96
+ ).toBe("off");
97
+ });
98
+
99
+ it("only exposes real Matrix thread ids in tool context", () => {
100
+ expect(
101
+ matrixPlugin.threading?.buildToolContext?.({
102
+ cfg: {} as CoreConfig,
103
+ context: {
104
+ To: "room:!room:example.org",
105
+ ReplyToId: "$reply",
106
+ },
107
+ hasRepliedRef: { value: false },
108
+ }),
109
+ ).toEqual({
110
+ currentChannelId: "room:!room:example.org",
111
+ currentThreadTs: undefined,
112
+ hasRepliedRef: { value: false },
113
+ });
114
+
115
+ expect(
116
+ matrixPlugin.threading?.buildToolContext?.({
117
+ cfg: {} as CoreConfig,
118
+ context: {
119
+ To: "room:!room:example.org",
120
+ ReplyToId: "$reply",
121
+ MessageThreadId: "$thread",
122
+ },
123
+ hasRepliedRef: { value: true },
124
+ }),
125
+ ).toEqual({
126
+ currentChannelId: "room:!room:example.org",
127
+ currentThreadTs: "$thread",
128
+ hasRepliedRef: { value: true },
129
+ });
130
+ });
131
+
132
+ it("exposes Matrix direct user id in dm tool context", () => {
133
+ expect(
134
+ matrixPlugin.threading?.buildToolContext?.({
135
+ cfg: {} as CoreConfig,
136
+ context: {
137
+ From: "matrix:@alice:example.org",
138
+ To: "room:!dm:example.org",
139
+ ChatType: "direct",
140
+ MessageThreadId: "$thread",
141
+ },
142
+ hasRepliedRef: { value: false },
143
+ }),
144
+ ).toEqual({
145
+ currentChannelId: "room:!dm:example.org",
146
+ currentThreadTs: "$thread",
147
+ currentDirectUserId: "@alice:example.org",
148
+ hasRepliedRef: { value: false },
149
+ });
150
+ });
151
+
152
+ it("accepts raw room ids when inferring Matrix direct user ids", () => {
153
+ expect(
154
+ matrixPlugin.threading?.buildToolContext?.({
155
+ cfg: {} as CoreConfig,
156
+ context: {
157
+ From: "user:@alice:example.org",
158
+ To: "!dm:example.org",
159
+ ChatType: "direct",
160
+ },
161
+ hasRepliedRef: { value: false },
162
+ }),
163
+ ).toEqual({
164
+ currentChannelId: "!dm:example.org",
165
+ currentThreadTs: undefined,
166
+ currentDirectUserId: "@alice:example.org",
167
+ hasRepliedRef: { value: false },
168
+ });
169
+ });
170
+
171
+ it("resolves group mention policy from account config", () => {
172
+ const cfg = {
173
+ channels: {
174
+ matrix: {
175
+ groups: {
176
+ "!room:example.org": { requireMention: true },
177
+ },
178
+ accounts: {
179
+ Assistant: {
180
+ groups: {
181
+ "!room:example.org": { requireMention: false },
182
+ },
183
+ },
184
+ },
185
+ },
186
+ },
187
+ } as unknown as CoreConfig;
188
+
189
+ expect(matrixPlugin.groups!.resolveRequireMention!({ cfg, groupId: "!room:example.org" })).toBe(
190
+ true,
191
+ );
192
+ expect(
193
+ matrixPlugin.groups!.resolveRequireMention!({
194
+ cfg,
195
+ accountId: "assistant",
196
+ groupId: "!room:example.org",
197
+ }),
198
+ ).toBe(false);
199
+
200
+ expect(
201
+ matrixPlugin.groups!.resolveRequireMention!({
202
+ cfg,
203
+ accountId: "assistant",
204
+ groupId: "matrix:room:!room:example.org",
205
+ }),
206
+ ).toBe(false);
207
+ });
208
+
209
+ it("matches prefixed Matrix aliases in group context", () => {
210
+ const cfg = {
211
+ channels: {
212
+ matrix: {
213
+ groups: {
214
+ "#ops:example.org": { requireMention: false },
215
+ },
216
+ },
217
+ },
218
+ } as unknown as CoreConfig;
219
+
220
+ expect(
221
+ matrixPlugin.groups!.resolveRequireMention!({
222
+ cfg,
223
+ groupId: "matrix:room:!room:example.org",
224
+ groupChannel: "matrix:channel:#ops:example.org",
225
+ }),
226
+ ).toBe(false);
227
+ });
228
+
229
+ it("reports room access warnings against the active Matrix config path", () => {
230
+ expect(
231
+ matrixPlugin.security?.collectWarnings?.({
232
+ cfg: {
233
+ channels: {
234
+ matrix: {
235
+ groupPolicy: "open",
236
+ },
237
+ },
238
+ } as CoreConfig,
239
+ account: resolveMatrixAccount({
240
+ cfg: {
241
+ channels: {
242
+ matrix: {
243
+ groupPolicy: "open",
244
+ },
245
+ },
246
+ } as CoreConfig,
247
+ accountId: "default",
248
+ }),
249
+ }),
250
+ ).toEqual([
251
+ '- Matrix rooms: groupPolicy="open" allows any room to trigger (mention-gated). Set channels.lobi.groupPolicy="allowlist" + channels.lobi.groups (and optionally channels.lobi.groupAllowFrom) to restrict rooms.',
252
+ ]);
253
+
254
+ expect(
255
+ matrixPlugin.security?.collectWarnings?.({
256
+ cfg: {
257
+ channels: {
258
+ matrix: {
259
+ defaultAccount: "assistant",
260
+ accounts: {
261
+ assistant: {
262
+ groupPolicy: "open",
263
+ },
264
+ },
265
+ },
266
+ },
267
+ } as CoreConfig,
268
+ account: resolveMatrixAccount({
269
+ cfg: {
270
+ channels: {
271
+ matrix: {
272
+ defaultAccount: "assistant",
273
+ accounts: {
274
+ assistant: {
275
+ groupPolicy: "open",
276
+ },
277
+ },
278
+ },
279
+ },
280
+ } as CoreConfig,
281
+ accountId: "assistant",
282
+ }),
283
+ }),
284
+ ).toEqual([
285
+ '- Matrix rooms: groupPolicy="open" allows any room to trigger (mention-gated). Set channels.lobi.accounts.assistant.groupPolicy="allowlist" + channels.lobi.accounts.assistant.groups (and optionally channels.lobi.accounts.assistant.groupAllowFrom) to restrict rooms.',
286
+ ]);
287
+ });
288
+
289
+ it("reports invite auto-join warnings only when explicitly enabled", () => {
290
+ expect(
291
+ matrixPlugin.security?.collectWarnings?.({
292
+ cfg: {
293
+ channels: {
294
+ matrix: {
295
+ groupPolicy: "allowlist",
296
+ autoJoin: "always",
297
+ },
298
+ },
299
+ } as CoreConfig,
300
+ account: resolveMatrixAccount({
301
+ cfg: {
302
+ channels: {
303
+ matrix: {
304
+ groupPolicy: "allowlist",
305
+ autoJoin: "always",
306
+ },
307
+ },
308
+ } as CoreConfig,
309
+ accountId: "default",
310
+ }),
311
+ }),
312
+ ).toEqual([
313
+ '- Matrix invites: autoJoin="always" joins any invited room before message policy applies. Set channels.lobi.autoJoin="allowlist" + channels.lobi.autoJoinAllowlist (or channels.lobi.autoJoin="off") to restrict joins.',
314
+ ]);
315
+ });
316
+
317
+ it("writes matrix non-default account credentials under channels.lobi.accounts", () => {
318
+ const cfg = {
319
+ channels: {
320
+ matrix: {
321
+ homeserver: "https://default.example.org",
322
+ accessToken: "default-token",
323
+ deviceId: "DEFAULTDEVICE",
324
+ avatarUrl: "mxc://server/avatar",
325
+ encryption: true,
326
+ threadReplies: "inbound",
327
+ groups: {
328
+ "!room:example.org": { requireMention: true },
329
+ },
330
+ },
331
+ },
332
+ } as unknown as CoreConfig;
333
+
334
+ const updated = matrixPlugin.setup!.applyAccountConfig({
335
+ cfg,
336
+ accountId: "ops",
337
+ input: {
338
+ homeserver: "https://matrix.example.org",
339
+ userId: "@ops:example.org",
340
+ accessToken: "ops-token",
341
+ },
342
+ }) as CoreConfig;
343
+
344
+ expect(updated.channels?.["matrix"]?.accessToken).toBeUndefined();
345
+ expect(updated.channels?.["matrix"]?.deviceId).toBeUndefined();
346
+ expect(updated.channels?.["matrix"]?.avatarUrl).toBeUndefined();
347
+ expect(updated.channels?.["matrix"]?.accounts?.default).toMatchObject({
348
+ accessToken: "default-token",
349
+ homeserver: "https://default.example.org",
350
+ deviceId: "DEFAULTDEVICE",
351
+ avatarUrl: "mxc://server/avatar",
352
+ encryption: true,
353
+ threadReplies: "inbound",
354
+ groups: {
355
+ "!room:example.org": { requireMention: true },
356
+ },
357
+ });
358
+ expect(updated.channels?.["matrix"]?.accounts?.ops).toMatchObject({
359
+ enabled: true,
360
+ homeserver: "https://matrix.example.org",
361
+ userId: "@ops:example.org",
362
+ accessToken: "ops-token",
363
+ });
364
+ expect(resolveMatrixConfigForAccount(updated, "ops", {})).toMatchObject({
365
+ homeserver: "https://matrix.example.org",
366
+ userId: "@ops:example.org",
367
+ accessToken: "ops-token",
368
+ deviceId: undefined,
369
+ });
370
+ });
371
+
372
+ it("writes default matrix account credentials under channels.lobi.accounts.default", () => {
373
+ const cfg = {
374
+ channels: {
375
+ matrix: {
376
+ homeserver: "https://legacy.example.org",
377
+ accessToken: "legacy-token",
378
+ },
379
+ },
380
+ } as unknown as CoreConfig;
381
+
382
+ const updated = matrixPlugin.setup!.applyAccountConfig({
383
+ cfg,
384
+ accountId: "default",
385
+ input: {
386
+ homeserver: "https://matrix.example.org",
387
+ userId: "@bot:example.org",
388
+ accessToken: "bot-token",
389
+ },
390
+ }) as CoreConfig;
391
+
392
+ expect(updated.channels?.["matrix"]).toMatchObject({
393
+ enabled: true,
394
+ homeserver: "https://matrix.example.org",
395
+ userId: "@bot:example.org",
396
+ accessToken: "bot-token",
397
+ });
398
+ expect(updated.channels?.["matrix"]?.accounts).toBeUndefined();
399
+ });
400
+
401
+ it("requires account-scoped env vars when --use-env is set for non-default accounts", () => {
402
+ const envKeys = [
403
+ "LOBI_OPS_HOMESERVER",
404
+ "MATRIX_OPS_USER_ID",
405
+ "LOBI_OPS_ACCESS_TOKEN",
406
+ "MATRIX_OPS_PASSWORD",
407
+ ] as const;
408
+ const previousEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])) as Record<
409
+ (typeof envKeys)[number],
410
+ string | undefined
411
+ >;
412
+ for (const key of envKeys) {
413
+ delete process.env[key];
414
+ }
415
+ try {
416
+ const error = matrixPlugin.setup!.validateInput?.({
417
+ cfg: {} as CoreConfig,
418
+ accountId: "ops",
419
+ input: { useEnv: true },
420
+ });
421
+ expect(error).toBe(
422
+ 'Set per-account env vars for "ops" (for example LOBI_OPS_HOMESERVER + LOBI_OPS_ACCESS_TOKEN or MATRIX_OPS_USER_ID + MATRIX_OPS_PASSWORD).',
423
+ );
424
+ } finally {
425
+ for (const key of envKeys) {
426
+ if (previousEnv[key] === undefined) {
427
+ delete process.env[key];
428
+ } else {
429
+ process.env[key] = previousEnv[key];
430
+ }
431
+ }
432
+ }
433
+ });
434
+
435
+ it("accepts --use-env for non-default account when scoped env vars are present", () => {
436
+ const envKeys = {
437
+ LOBI_OPS_HOMESERVER: process.env.LOBI_OPS_HOMESERVER,
438
+ LOBI_OPS_ACCESS_TOKEN: process.env.LOBI_OPS_ACCESS_TOKEN,
439
+ };
440
+ process.env.LOBI_OPS_HOMESERVER = "https://ops.example.org";
441
+ process.env.LOBI_OPS_ACCESS_TOKEN = "ops-token";
442
+ try {
443
+ const error = matrixPlugin.setup!.validateInput?.({
444
+ cfg: {} as CoreConfig,
445
+ accountId: "ops",
446
+ input: { useEnv: true },
447
+ });
448
+ expect(error).toBeNull();
449
+ } finally {
450
+ for (const [key, value] of Object.entries(envKeys)) {
451
+ if (value === undefined) {
452
+ delete process.env[key];
453
+ } else {
454
+ process.env[key] = value;
455
+ }
456
+ }
457
+ }
458
+ });
459
+
460
+ it("clears stored auth fields when switching a Matrix account to env-backed auth", () => {
461
+ const envKeys = {
462
+ LOBI_OPS_HOMESERVER: process.env.LOBI_OPS_HOMESERVER,
463
+ LOBI_OPS_ACCESS_TOKEN: process.env.LOBI_OPS_ACCESS_TOKEN,
464
+ LOBI_OPS_DEVICE_ID: process.env.LOBI_OPS_DEVICE_ID,
465
+ LOBI_OPS_DEVICE_NAME: process.env.LOBI_OPS_DEVICE_NAME,
466
+ };
467
+ process.env.LOBI_OPS_HOMESERVER = "https://ops.env.example.org";
468
+ process.env.LOBI_OPS_ACCESS_TOKEN = "ops-env-token";
469
+ process.env.LOBI_OPS_DEVICE_ID = "OPSENVDEVICE";
470
+ process.env.LOBI_OPS_DEVICE_NAME = "Ops Env Device";
471
+
472
+ try {
473
+ const cfg = {
474
+ channels: {
475
+ matrix: {
476
+ accounts: {
477
+ ops: {
478
+ homeserver: "https://ops.inline.example.org",
479
+ userId: "@ops:inline.example.org",
480
+ accessToken: "ops-inline-token",
481
+ password: "ops-inline-password", // pragma: allowlist secret
482
+ deviceId: "OPSINLINEDEVICE",
483
+ deviceName: "Ops Inline Device",
484
+ encryption: true,
485
+ },
486
+ },
487
+ },
488
+ },
489
+ } as unknown as CoreConfig;
490
+
491
+ const updated = matrixPlugin.setup!.applyAccountConfig({
492
+ cfg,
493
+ accountId: "ops",
494
+ input: {
495
+ useEnv: true,
496
+ name: "Ops",
497
+ },
498
+ }) as CoreConfig;
499
+
500
+ expect(updated.channels?.["matrix"]?.accounts?.ops).toMatchObject({
501
+ name: "Ops",
502
+ enabled: true,
503
+ encryption: true,
504
+ });
505
+ expect(updated.channels?.["matrix"]?.accounts?.ops?.homeserver).toBeUndefined();
506
+ expect(updated.channels?.["matrix"]?.accounts?.ops?.userId).toBeUndefined();
507
+ expect(updated.channels?.["matrix"]?.accounts?.ops?.accessToken).toBeUndefined();
508
+ expect(updated.channels?.["matrix"]?.accounts?.ops?.password).toBeUndefined();
509
+ expect(updated.channels?.["matrix"]?.accounts?.ops?.deviceId).toBeUndefined();
510
+ expect(updated.channels?.["matrix"]?.accounts?.ops?.deviceName).toBeUndefined();
511
+ expect(resolveMatrixConfigForAccount(updated, "ops", process.env)).toMatchObject({
512
+ homeserver: "https://ops.env.example.org",
513
+ accessToken: "ops-env-token",
514
+ deviceId: "OPSENVDEVICE",
515
+ deviceName: "Ops Env Device",
516
+ });
517
+ } finally {
518
+ for (const [key, value] of Object.entries(envKeys)) {
519
+ if (value === undefined) {
520
+ delete process.env[key];
521
+ } else {
522
+ process.env[key] = value;
523
+ }
524
+ }
525
+ }
526
+ });
527
+
528
+ it("resolves account id from input name when explicit account id is missing", () => {
529
+ const accountId = matrixPlugin.setup!.resolveAccountId?.({
530
+ cfg: {} as CoreConfig,
531
+ accountId: undefined,
532
+ input: { name: "Main Bot" },
533
+ });
534
+ expect(accountId).toBe("main-bot");
535
+ });
536
+
537
+ it("resolves binding account id from agent id when omitted", () => {
538
+ const accountId = matrixPlugin.setup!.resolveBindingAccountId?.({
539
+ cfg: {} as CoreConfig,
540
+ agentId: "Ops",
541
+ accountId: undefined,
542
+ });
543
+ expect(accountId).toBe("ops");
544
+ });
545
+
546
+ it("clears stale access token when switching an account to password auth", () => {
547
+ const cfg = {
548
+ channels: {
549
+ matrix: {
550
+ accounts: {
551
+ default: {
552
+ homeserver: "https://matrix.example.org",
553
+ accessToken: "old-token",
554
+ },
555
+ },
556
+ },
557
+ },
558
+ } as unknown as CoreConfig;
559
+
560
+ const updated = matrixPlugin.setup!.applyAccountConfig({
561
+ cfg,
562
+ accountId: "default",
563
+ input: {
564
+ homeserver: "https://matrix.example.org",
565
+ userId: "@bot:example.org",
566
+ password: "new-password", // pragma: allowlist secret
567
+ },
568
+ }) as CoreConfig;
569
+
570
+ expect(updated.channels?.["matrix"]?.accounts?.default?.password).toBe("new-password");
571
+ expect(updated.channels?.["matrix"]?.accounts?.default?.accessToken).toBeUndefined();
572
+ });
573
+
574
+ it("clears stale password when switching an account to token auth", () => {
575
+ const cfg = {
576
+ channels: {
577
+ matrix: {
578
+ accounts: {
579
+ default: {
580
+ homeserver: "https://matrix.example.org",
581
+ userId: "@bot:example.org",
582
+ password: "old-password", // pragma: allowlist secret
583
+ },
584
+ },
585
+ },
586
+ },
587
+ } as unknown as CoreConfig;
588
+
589
+ const updated = matrixPlugin.setup!.applyAccountConfig({
590
+ cfg,
591
+ accountId: "default",
592
+ input: {
593
+ homeserver: "https://matrix.example.org",
594
+ accessToken: "new-token",
595
+ },
596
+ }) as CoreConfig;
597
+
598
+ expect(updated.channels?.["matrix"]?.accounts?.default?.accessToken).toBe("new-token");
599
+ expect(updated.channels?.["matrix"]?.accounts?.default?.password).toBeUndefined();
600
+ });
601
+ });
@@ -0,0 +1,38 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { createNonExitingRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
3
+
4
+ const resolveMatrixTargetsMock = vi.hoisted(() => vi.fn(async () => []));
5
+
6
+ vi.mock("./resolve-targets.js", () => ({
7
+ resolveMatrixTargets: resolveMatrixTargetsMock,
8
+ }));
9
+
10
+ import { matrixResolverAdapter } from "./resolver.js";
11
+
12
+ describe("matrix resolver adapter", () => {
13
+ beforeEach(() => {
14
+ resolveMatrixTargetsMock.mockClear();
15
+ });
16
+
17
+ it("forwards accountId into Matrix target resolution", async () => {
18
+ await matrixResolverAdapter.resolveTargets({
19
+ cfg: { channels: { matrix: {} } },
20
+ accountId: "ops",
21
+ inputs: ["Alice"],
22
+ kind: "user",
23
+ runtime: createNonExitingRuntimeEnv(),
24
+ });
25
+
26
+ expect(resolveMatrixTargetsMock).toHaveBeenCalledWith({
27
+ cfg: { channels: { matrix: {} } },
28
+ accountId: "ops",
29
+ inputs: ["Alice"],
30
+ kind: "user",
31
+ runtime: expect.objectContaining({
32
+ log: expect.any(Function),
33
+ error: expect.any(Function),
34
+ exit: expect.any(Function),
35
+ }),
36
+ });
37
+ });
38
+ });
@@ -0,0 +1,16 @@
1
+ import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./directory-live.js";
2
+ import { resolveMatrixAuth } from "./matrix/client.js";
3
+ import { probeMatrix } from "./matrix/probe.js";
4
+ import { sendMessageMatrix } from "./matrix/send.js";
5
+ import { matrixOutbound } from "./outbound.js";
6
+ import { resolveMatrixTargets } from "./resolve-targets.js";
7
+
8
+ export const matrixChannelRuntime = {
9
+ listMatrixDirectoryGroupsLive,
10
+ listMatrixDirectoryPeersLive,
11
+ matrixOutbound,
12
+ probeMatrix,
13
+ resolveMatrixAuth,
14
+ resolveMatrixTargets,
15
+ sendMessageMatrix,
16
+ };