@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,115 @@
1
+ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const ensureMatrixSdkLoggingConfiguredMock = vi.hoisted(() => vi.fn());
4
+ const resolveValidatedMatrixHomeserverUrlMock = vi.hoisted(() => vi.fn());
5
+ const maybeMigrateLegacyStorageMock = vi.hoisted(() => vi.fn(async () => undefined));
6
+ const resolveMatrixStoragePathsMock = vi.hoisted(() => vi.fn());
7
+ const writeStorageMetaMock = vi.hoisted(() => vi.fn());
8
+ const MatrixClientMock = vi.hoisted(() => vi.fn());
9
+
10
+ vi.mock("./logging.js", () => ({
11
+ ensureMatrixSdkLoggingConfigured: ensureMatrixSdkLoggingConfiguredMock,
12
+ }));
13
+
14
+ vi.mock("./config.js", () => ({
15
+ resolveValidatedMatrixHomeserverUrl: resolveValidatedMatrixHomeserverUrlMock,
16
+ }));
17
+
18
+ vi.mock("./storage.js", () => ({
19
+ maybeMigrateLegacyStorage: maybeMigrateLegacyStorageMock,
20
+ resolveMatrixStoragePaths: resolveMatrixStoragePathsMock,
21
+ writeStorageMeta: writeStorageMetaMock,
22
+ }));
23
+
24
+ vi.mock("../sdk.js", () => ({
25
+ MatrixClient: MatrixClientMock,
26
+ }));
27
+
28
+ let createMatrixClient: typeof import("./create-client.js").createMatrixClient;
29
+
30
+ describe("createMatrixClient", () => {
31
+ const storagePaths = {
32
+ rootDir: "/tmp/openclaw-matrix-create-client-test",
33
+ storagePath: "/tmp/openclaw-matrix-create-client-test/storage.json",
34
+ recoveryKeyPath: "/tmp/openclaw-matrix-create-client-test/recovery.key",
35
+ idbSnapshotPath: "/tmp/openclaw-matrix-create-client-test/idb.snapshot",
36
+ metaPath: "/tmp/openclaw-matrix-create-client-test/storage-meta.json",
37
+ accountKey: "default",
38
+ tokenHash: "token-hash",
39
+ };
40
+
41
+ beforeAll(async () => {
42
+ ({ createMatrixClient } = await import("./create-client.js"));
43
+ });
44
+
45
+ beforeEach(() => {
46
+ vi.clearAllMocks();
47
+ ensureMatrixSdkLoggingConfiguredMock.mockReturnValue(undefined);
48
+ resolveValidatedMatrixHomeserverUrlMock.mockResolvedValue("https://matrix.example.org");
49
+ resolveMatrixStoragePathsMock.mockReturnValue(storagePaths);
50
+ MatrixClientMock.mockImplementation(function MockMatrixClient() {
51
+ return {
52
+ stop: vi.fn(),
53
+ };
54
+ });
55
+ });
56
+
57
+ it("persists storage metadata by default", async () => {
58
+ await createMatrixClient({
59
+ homeserver: "https://matrix.example.org",
60
+ userId: "@bot:example.org",
61
+ accessToken: "tok",
62
+ });
63
+
64
+ expect(writeStorageMetaMock).toHaveBeenCalledWith({
65
+ storagePaths,
66
+ homeserver: "https://matrix.example.org",
67
+ userId: "@bot:example.org",
68
+ accountId: undefined,
69
+ deviceId: undefined,
70
+ });
71
+ expect(resolveMatrixStoragePathsMock).toHaveBeenCalledTimes(1);
72
+ expect(MatrixClientMock).toHaveBeenCalledWith("https://matrix.example.org", "tok", {
73
+ userId: "@bot:example.org",
74
+ password: undefined,
75
+ deviceId: undefined,
76
+ encryption: undefined,
77
+ localTimeoutMs: undefined,
78
+ initialSyncLimit: undefined,
79
+ storagePath: storagePaths.storagePath,
80
+ recoveryKeyPath: storagePaths.recoveryKeyPath,
81
+ idbSnapshotPath: storagePaths.idbSnapshotPath,
82
+ cryptoDatabasePrefix: "openclaw-matrix-default-token-hash",
83
+ autoBootstrapCrypto: undefined,
84
+ ssrfPolicy: undefined,
85
+ dispatcherPolicy: undefined,
86
+ });
87
+ });
88
+
89
+ it("skips persistent storage wiring when persistence is disabled", async () => {
90
+ await createMatrixClient({
91
+ homeserver: "https://matrix.example.org",
92
+ userId: "@bot:example.org",
93
+ accessToken: "tok",
94
+ persistStorage: false,
95
+ });
96
+
97
+ expect(resolveMatrixStoragePathsMock).not.toHaveBeenCalled();
98
+ expect(writeStorageMetaMock).not.toHaveBeenCalled();
99
+ expect(MatrixClientMock).toHaveBeenCalledWith("https://matrix.example.org", "tok", {
100
+ userId: "@bot:example.org",
101
+ password: undefined,
102
+ deviceId: undefined,
103
+ encryption: undefined,
104
+ localTimeoutMs: undefined,
105
+ initialSyncLimit: undefined,
106
+ storagePath: undefined,
107
+ recoveryKeyPath: undefined,
108
+ idbSnapshotPath: undefined,
109
+ cryptoDatabasePrefix: undefined,
110
+ autoBootstrapCrypto: undefined,
111
+ ssrfPolicy: undefined,
112
+ dispatcherPolicy: undefined,
113
+ });
114
+ });
115
+ });
@@ -0,0 +1,101 @@
1
+ import fs from "node:fs";
2
+ import type { PinnedDispatcherPolicy } from "openclaw/plugin-sdk/infra-runtime";
3
+ import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
4
+ import type { SsrFPolicy } from "../../runtime-api.js";
5
+ import type { MatrixClient } from "../sdk.js";
6
+ import { resolveValidatedMatrixHomeserverUrl } from "./config.js";
7
+ import {
8
+ maybeMigrateLegacyStorage,
9
+ resolveMatrixStoragePaths,
10
+ writeStorageMeta,
11
+ } from "./storage.js";
12
+
13
+ type MatrixCreateClientRuntimeDeps = {
14
+ MatrixClient: typeof import("../sdk.js").MatrixClient;
15
+ ensureMatrixSdkLoggingConfigured: typeof import("./logging.js").ensureMatrixSdkLoggingConfigured;
16
+ };
17
+
18
+ let matrixCreateClientRuntimeDepsPromise: Promise<MatrixCreateClientRuntimeDeps> | undefined;
19
+
20
+ async function loadMatrixCreateClientRuntimeDeps(): Promise<MatrixCreateClientRuntimeDeps> {
21
+ matrixCreateClientRuntimeDepsPromise ??= Promise.all([
22
+ import("../sdk.js"),
23
+ import("./logging.js"),
24
+ ]).then(([sdkModule, loggingModule]) => ({
25
+ MatrixClient: sdkModule.MatrixClient,
26
+ ensureMatrixSdkLoggingConfigured: loggingModule.ensureMatrixSdkLoggingConfigured,
27
+ }));
28
+ return await matrixCreateClientRuntimeDepsPromise;
29
+ }
30
+
31
+ export async function createMatrixClient(params: {
32
+ homeserver: string;
33
+ userId?: string;
34
+ accessToken: string;
35
+ password?: string;
36
+ deviceId?: string;
37
+ persistStorage?: boolean;
38
+ encryption?: boolean;
39
+ localTimeoutMs?: number;
40
+ initialSyncLimit?: number;
41
+ accountId?: string | null;
42
+ autoBootstrapCrypto?: boolean;
43
+ allowPrivateNetwork?: boolean;
44
+ ssrfPolicy?: SsrFPolicy;
45
+ dispatcherPolicy?: PinnedDispatcherPolicy;
46
+ }): Promise<MatrixClient> {
47
+ const { MatrixClient, ensureMatrixSdkLoggingConfigured } =
48
+ await loadMatrixCreateClientRuntimeDeps();
49
+ ensureMatrixSdkLoggingConfigured();
50
+ const homeserver = await resolveValidatedMatrixHomeserverUrl(params.homeserver, {
51
+ dangerouslyAllowPrivateNetwork: params.allowPrivateNetwork,
52
+ });
53
+ const matrixClientUserId = normalizeOptionalString(params.userId);
54
+ const userId = matrixClientUserId ?? "unknown";
55
+ const persistStorage = params.persistStorage !== false;
56
+ const storagePaths = persistStorage
57
+ ? resolveMatrixStoragePaths({
58
+ homeserver,
59
+ userId,
60
+ accessToken: params.accessToken,
61
+ accountId: params.accountId,
62
+ deviceId: params.deviceId,
63
+ env: process.env,
64
+ })
65
+ : null;
66
+
67
+ if (storagePaths) {
68
+ await maybeMigrateLegacyStorage({
69
+ storagePaths,
70
+ env: process.env,
71
+ });
72
+ fs.mkdirSync(storagePaths.rootDir, { recursive: true });
73
+ writeStorageMeta({
74
+ storagePaths,
75
+ homeserver,
76
+ userId,
77
+ accountId: params.accountId,
78
+ deviceId: params.deviceId,
79
+ });
80
+ }
81
+
82
+ const cryptoDatabasePrefix = storagePaths
83
+ ? `openclaw-matrix-${storagePaths.accountKey}-${storagePaths.tokenHash}`
84
+ : undefined;
85
+
86
+ return new MatrixClient(homeserver, params.accessToken, {
87
+ userId: matrixClientUserId,
88
+ password: params.password,
89
+ deviceId: params.deviceId,
90
+ encryption: params.encryption,
91
+ localTimeoutMs: params.localTimeoutMs,
92
+ initialSyncLimit: params.initialSyncLimit,
93
+ storagePath: storagePaths?.storagePath,
94
+ recoveryKeyPath: storagePaths?.recoveryKeyPath,
95
+ idbSnapshotPath: storagePaths?.idbSnapshotPath,
96
+ cryptoDatabasePrefix,
97
+ autoBootstrapCrypto: params.autoBootstrapCrypto,
98
+ ssrfPolicy: params.ssrfPolicy,
99
+ dispatcherPolicy: params.dispatcherPolicy,
100
+ });
101
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ hasReadyMatrixEnvAuth,
3
+ resolveGlobalMatrixEnvConfig,
4
+ resolveMatrixEnvAuthReadiness,
5
+ resolveScopedMatrixEnvConfig,
6
+ } from "./config.js";
@@ -0,0 +1,265 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import type { ISyncResponse } from "@archipelagolab/lobi-js-sdk";
5
+ import * as jsonStore from "openclaw/plugin-sdk/json-store";
6
+ import { afterEach, describe, expect, it, vi } from "vitest";
7
+ import { FileBackedMatrixSyncStore } from "./file-sync-store.js";
8
+
9
+ function createSyncResponse(nextBatch: string): ISyncResponse {
10
+ return {
11
+ next_batch: nextBatch,
12
+ rooms: {
13
+ join: {
14
+ "!room:example.org": {
15
+ summary: {
16
+ "m.heroes": [],
17
+ },
18
+ state: { events: [] },
19
+ timeline: {
20
+ events: [
21
+ {
22
+ content: {
23
+ body: "hello",
24
+ msgtype: "m.text",
25
+ },
26
+ event_id: "$message",
27
+ origin_server_ts: 1,
28
+ sender: "@user:example.org",
29
+ type: "m.room.message",
30
+ },
31
+ ],
32
+ prev_batch: "t0",
33
+ },
34
+ ephemeral: { events: [] },
35
+ account_data: { events: [] },
36
+ unread_notifications: {},
37
+ },
38
+ },
39
+ invite: {},
40
+ leave: {},
41
+ knock: {},
42
+ },
43
+ account_data: {
44
+ events: [
45
+ {
46
+ content: { theme: "dark" },
47
+ type: "com.openclaw.test",
48
+ },
49
+ ],
50
+ },
51
+ };
52
+ }
53
+
54
+ function createDeferred() {
55
+ let resolve!: () => void;
56
+ const promise = new Promise<void>((resolvePromise) => {
57
+ resolve = resolvePromise;
58
+ });
59
+ return { promise, resolve };
60
+ }
61
+
62
+ describe("FileBackedMatrixSyncStore", () => {
63
+ const tempDirs: string[] = [];
64
+
65
+ function createStoragePath(): string {
66
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-sync-store-"));
67
+ tempDirs.push(tempDir);
68
+ return path.join(tempDir, "bot-storage.json");
69
+ }
70
+
71
+ afterEach(() => {
72
+ vi.restoreAllMocks();
73
+ vi.useRealTimers();
74
+ for (const dir of tempDirs.splice(0)) {
75
+ fs.rmSync(dir, { recursive: true, force: true });
76
+ }
77
+ });
78
+
79
+ it("persists sync data so restart resumes from the saved cursor", async () => {
80
+ const storagePath = createStoragePath();
81
+
82
+ const firstStore = new FileBackedMatrixSyncStore(storagePath);
83
+ expect(firstStore.hasSavedSync()).toBe(false);
84
+ await firstStore.setSyncData(createSyncResponse("s123"));
85
+ await firstStore.flush();
86
+
87
+ const secondStore = new FileBackedMatrixSyncStore(storagePath);
88
+ expect(secondStore.hasSavedSync()).toBe(true);
89
+ await expect(secondStore.getSavedSyncToken()).resolves.toBe("s123");
90
+
91
+ const savedSync = await secondStore.getSavedSync();
92
+ expect(savedSync?.nextBatch).toBe("s123");
93
+ expect(savedSync?.accountData).toEqual([
94
+ {
95
+ content: { theme: "dark" },
96
+ type: "com.openclaw.test",
97
+ },
98
+ ]);
99
+ expect(savedSync?.roomsData.join?.["!room:example.org"]).toBeTruthy();
100
+ expect(secondStore.hasSavedSyncFromCleanShutdown()).toBe(false);
101
+ });
102
+
103
+ it("claims current-token storage ownership when sync state is persisted", async () => {
104
+ const storagePath = createStoragePath();
105
+ const rootDir = path.dirname(storagePath);
106
+ fs.writeFileSync(
107
+ path.join(rootDir, "storage-meta.json"),
108
+ JSON.stringify({
109
+ homeserver: "https://matrix.example.org",
110
+ userId: "@bot:example.org",
111
+ accountId: "default",
112
+ accessTokenHash: "token-hash",
113
+ deviceId: null,
114
+ }),
115
+ "utf8",
116
+ );
117
+
118
+ const store = new FileBackedMatrixSyncStore(storagePath);
119
+ await store.setSyncData(createSyncResponse("claimed-token"));
120
+ await store.flush();
121
+
122
+ const meta = JSON.parse(fs.readFileSync(path.join(rootDir, "storage-meta.json"), "utf8")) as {
123
+ currentTokenStateClaimed?: boolean;
124
+ };
125
+ expect(meta.currentTokenStateClaimed).toBe(true);
126
+ });
127
+
128
+ it("only treats sync state as restart-safe after a clean shutdown persist", async () => {
129
+ const storagePath = createStoragePath();
130
+
131
+ const firstStore = new FileBackedMatrixSyncStore(storagePath);
132
+ await firstStore.setSyncData(createSyncResponse("s123"));
133
+ await firstStore.flush();
134
+
135
+ const afterDirtyPersist = new FileBackedMatrixSyncStore(storagePath);
136
+ expect(afterDirtyPersist.hasSavedSync()).toBe(true);
137
+ expect(afterDirtyPersist.hasSavedSyncFromCleanShutdown()).toBe(false);
138
+
139
+ firstStore.markCleanShutdown();
140
+ await firstStore.flush();
141
+
142
+ const afterCleanShutdown = new FileBackedMatrixSyncStore(storagePath);
143
+ expect(afterCleanShutdown.hasSavedSync()).toBe(true);
144
+ expect(afterCleanShutdown.hasSavedSyncFromCleanShutdown()).toBe(true);
145
+ });
146
+
147
+ it("clears the clean-shutdown marker once fresh sync data arrives", async () => {
148
+ const storagePath = createStoragePath();
149
+
150
+ const firstStore = new FileBackedMatrixSyncStore(storagePath);
151
+ await firstStore.setSyncData(createSyncResponse("s123"));
152
+ firstStore.markCleanShutdown();
153
+ await firstStore.flush();
154
+
155
+ const restartedStore = new FileBackedMatrixSyncStore(storagePath);
156
+ expect(restartedStore.hasSavedSyncFromCleanShutdown()).toBe(true);
157
+
158
+ await restartedStore.setSyncData(createSyncResponse("s456"));
159
+ await restartedStore.flush();
160
+
161
+ const afterNewSync = new FileBackedMatrixSyncStore(storagePath);
162
+ expect(afterNewSync.hasSavedSync()).toBe(true);
163
+ expect(afterNewSync.hasSavedSyncFromCleanShutdown()).toBe(false);
164
+ await expect(afterNewSync.getSavedSyncToken()).resolves.toBe("s456");
165
+ });
166
+
167
+ it("coalesces background persistence until the debounce window elapses", async () => {
168
+ vi.useFakeTimers();
169
+ const storagePath = createStoragePath();
170
+ const writeSpy = vi.spyOn(jsonStore, "writeJsonFileAtomically").mockResolvedValue();
171
+
172
+ const store = new FileBackedMatrixSyncStore(storagePath);
173
+ await store.setSyncData(createSyncResponse("s111"));
174
+ await store.setSyncData(createSyncResponse("s222"));
175
+ await store.storeClientOptions({ lazyLoadMembers: true });
176
+
177
+ expect(writeSpy).not.toHaveBeenCalled();
178
+
179
+ await vi.advanceTimersByTimeAsync(249);
180
+ expect(writeSpy).not.toHaveBeenCalled();
181
+
182
+ await vi.advanceTimersByTimeAsync(1);
183
+ await Promise.resolve();
184
+ expect(writeSpy).toHaveBeenCalledTimes(1);
185
+ expect(writeSpy).toHaveBeenCalledWith(
186
+ storagePath,
187
+ expect.objectContaining({
188
+ savedSync: expect.objectContaining({
189
+ nextBatch: "s222",
190
+ }),
191
+ clientOptions: {
192
+ lazyLoadMembers: true,
193
+ },
194
+ }),
195
+ );
196
+
197
+ await store.flush();
198
+ });
199
+
200
+ it("waits for an in-flight persist when shutdown flush runs", async () => {
201
+ vi.useFakeTimers();
202
+ const storagePath = createStoragePath();
203
+ const writeDeferred = createDeferred();
204
+ const writeSpy = vi
205
+ .spyOn(jsonStore, "writeJsonFileAtomically")
206
+ .mockImplementation(async () => writeDeferred.promise);
207
+
208
+ const store = new FileBackedMatrixSyncStore(storagePath);
209
+ await store.setSyncData(createSyncResponse("s777"));
210
+ await vi.advanceTimersByTimeAsync(250);
211
+
212
+ let flushCompleted = false;
213
+ const flushPromise = store.flush().then(() => {
214
+ flushCompleted = true;
215
+ });
216
+
217
+ await Promise.resolve();
218
+ expect(writeSpy).toHaveBeenCalledTimes(1);
219
+ expect(flushCompleted).toBe(false);
220
+
221
+ writeDeferred.resolve();
222
+ await flushPromise;
223
+ expect(flushCompleted).toBe(true);
224
+ });
225
+
226
+ it("persists client options alongside sync state", async () => {
227
+ const storagePath = createStoragePath();
228
+
229
+ const firstStore = new FileBackedMatrixSyncStore(storagePath);
230
+ await firstStore.storeClientOptions({ lazyLoadMembers: true });
231
+ await firstStore.flush();
232
+
233
+ const secondStore = new FileBackedMatrixSyncStore(storagePath);
234
+ await expect(secondStore.getClientOptions()).resolves.toEqual({ lazyLoadMembers: true });
235
+ });
236
+
237
+ it("loads legacy raw sync payloads from bot-storage.json", async () => {
238
+ const storagePath = createStoragePath();
239
+
240
+ fs.writeFileSync(
241
+ storagePath,
242
+ JSON.stringify({
243
+ next_batch: "legacy-token",
244
+ rooms: {
245
+ join: {},
246
+ },
247
+ account_data: {
248
+ events: [],
249
+ },
250
+ }),
251
+ "utf8",
252
+ );
253
+
254
+ const store = new FileBackedMatrixSyncStore(storagePath);
255
+ expect(store.hasSavedSync()).toBe(true);
256
+ await expect(store.getSavedSyncToken()).resolves.toBe("legacy-token");
257
+ await expect(store.getSavedSync()).resolves.toMatchObject({
258
+ nextBatch: "legacy-token",
259
+ roomsData: {
260
+ join: {},
261
+ },
262
+ accountData: [],
263
+ });
264
+ });
265
+ });