@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,982 @@
1
+ import { formatErrorMessage, type PinnedDispatcherPolicy } from "openclaw/plugin-sdk/infra-runtime";
2
+ import { coerceSecretRef } from "openclaw/plugin-sdk/provider-auth";
3
+ import { retryAsync } from "openclaw/plugin-sdk/retry-runtime";
4
+ import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
5
+ import {
6
+ requiresExplicitMatrixDefaultAccount,
7
+ resolveMatrixDefaultOrOnlyAccountId,
8
+ } from "../../account-selection.js";
9
+ import { resolveMatrixAccountStringValues } from "../../auth-precedence.js";
10
+ import { getLobiScopedEnvVarNames as getMatrixScopedEnvVarNames } from "../../env-vars.js";
11
+ import { getMatrixRuntime } from "../../runtime.js";
12
+ import type { CoreConfig } from "../../types.js";
13
+ import {
14
+ findMatrixAccountConfig,
15
+ resolveMatrixBaseConfig,
16
+ listNormalizedMatrixAccountIds,
17
+ } from "../account-config.js";
18
+ import { resolveMatrixConfigFieldPath } from "../config-paths.js";
19
+ import type { MatrixStoredCredentials } from "../credentials-read.js";
20
+ import {
21
+ DEFAULT_ACCOUNT_ID,
22
+ assertHttpUrlTargetsPrivateNetwork,
23
+ isPrivateOrLoopbackHost,
24
+ isPrivateNetworkOptInEnabled,
25
+ type LookupFn,
26
+ normalizeAccountId,
27
+ normalizeOptionalAccountId,
28
+ ssrfPolicyFromDangerouslyAllowPrivateNetwork,
29
+ } from "./config-runtime-api.js";
30
+ import { repairCurrentTokenStorageMetaDeviceId } from "./storage.js";
31
+ import type { MatrixAuth, MatrixResolvedConfig } from "./types.js";
32
+
33
+ type MatrixAuthClientDeps = {
34
+ MatrixClient: typeof import("../sdk.js").MatrixClient;
35
+ ensureMatrixSdkLoggingConfigured: typeof import("./logging.js").ensureMatrixSdkLoggingConfigured;
36
+ };
37
+
38
+ type MatrixCredentialsReadDeps = {
39
+ loadMatrixCredentials: typeof import("../credentials-read.js").loadMatrixCredentials;
40
+ credentialsMatchConfig: typeof import("../credentials-read.js").credentialsMatchConfig;
41
+ };
42
+
43
+ type MatrixSecretInputDeps = {
44
+ resolveConfiguredSecretInputString: typeof import("./config-secret-input.runtime.js").resolveConfiguredSecretInputString;
45
+ };
46
+
47
+ let matrixAuthClientDepsPromise: Promise<MatrixAuthClientDeps> | undefined;
48
+ let matrixCredentialsReadDepsPromise: Promise<MatrixCredentialsReadDeps> | undefined;
49
+ let matrixSecretInputDepsPromise: Promise<MatrixSecretInputDeps> | undefined;
50
+ let matrixAuthClientDepsForTest: MatrixAuthClientDeps | undefined;
51
+
52
+ const MATRIX_AUTH_REQUEST_RETRY_RE =
53
+ /\b(fetch failed|econnreset|econnrefused|enotfound|etimedout|ehostunreach|enetunreach|eai_again|und_err_|socket hang up|network|headers timeout|body timeout|connect timeout)\b/i;
54
+
55
+ export function setMatrixAuthClientDepsForTest(deps?: {
56
+ MatrixClient: typeof import("../sdk.js").MatrixClient;
57
+ ensureMatrixSdkLoggingConfigured: typeof import("./logging.js").ensureMatrixSdkLoggingConfigured;
58
+ }): void {
59
+ matrixAuthClientDepsForTest = deps;
60
+ }
61
+
62
+ async function loadMatrixAuthClientDeps(): Promise<MatrixAuthClientDeps> {
63
+ if (matrixAuthClientDepsForTest) {
64
+ return matrixAuthClientDepsForTest;
65
+ }
66
+ matrixAuthClientDepsPromise ??= Promise.all([import("../sdk.js"), import("./logging.js")]).then(
67
+ ([sdkModule, loggingModule]) => ({
68
+ MatrixClient: sdkModule.MatrixClient,
69
+ ensureMatrixSdkLoggingConfigured: loggingModule.ensureMatrixSdkLoggingConfigured,
70
+ }),
71
+ );
72
+ return await matrixAuthClientDepsPromise;
73
+ }
74
+
75
+ async function loadMatrixCredentialsReadDeps(): Promise<MatrixCredentialsReadDeps> {
76
+ matrixCredentialsReadDepsPromise ??= import("../credentials-read.js").then(
77
+ (credentialsReadModule) => ({
78
+ loadMatrixCredentials: credentialsReadModule.loadMatrixCredentials,
79
+ credentialsMatchConfig: credentialsReadModule.credentialsMatchConfig,
80
+ }),
81
+ );
82
+ return await matrixCredentialsReadDepsPromise;
83
+ }
84
+
85
+ async function loadMatrixSecretInputDeps(): Promise<MatrixSecretInputDeps> {
86
+ matrixSecretInputDepsPromise ??= import("./config-secret-input.runtime.js").then((runtime) => ({
87
+ resolveConfiguredSecretInputString: runtime.resolveConfiguredSecretInputString,
88
+ }));
89
+ return await matrixSecretInputDepsPromise;
90
+ }
91
+
92
+ function shouldRetryMatrixAuthRequest(err: unknown): boolean {
93
+ return MATRIX_AUTH_REQUEST_RETRY_RE.test(formatErrorMessage(err));
94
+ }
95
+
96
+ function isAbortSignalTriggered(signal?: AbortSignal): boolean {
97
+ return signal?.aborted === true;
98
+ }
99
+
100
+ function credentialsMatchBackfillAuthLineage(params: {
101
+ stored: MatrixStoredCredentials | null;
102
+ auth: Pick<MatrixAuth, "homeserver" | "userId" | "accessToken">;
103
+ }): boolean {
104
+ if (!params.stored) {
105
+ return true;
106
+ }
107
+ return (
108
+ params.stored.homeserver === params.auth.homeserver &&
109
+ params.stored.userId === params.auth.userId &&
110
+ params.stored.accessToken === params.auth.accessToken
111
+ );
112
+ }
113
+
114
+ async function retryMatrixAuthRequest<T>(label: string, run: () => Promise<T>): Promise<T> {
115
+ return await retryAsync(run, {
116
+ attempts: 3,
117
+ minDelayMs: 250,
118
+ maxDelayMs: 1_500,
119
+ jitter: 0.1,
120
+ label,
121
+ shouldRetry: (err) => shouldRetryMatrixAuthRequest(err),
122
+ });
123
+ }
124
+
125
+ async function fetchMatrixWhoamiIdentity(params: {
126
+ homeserver: string;
127
+ accessToken: string;
128
+ userId?: string;
129
+ ssrfPolicy?: MatrixResolvedConfig["ssrfPolicy"];
130
+ dispatcherPolicy?: PinnedDispatcherPolicy;
131
+ }): Promise<{
132
+ user_id?: string;
133
+ device_id?: string;
134
+ }> {
135
+ const { MatrixClient, ensureMatrixSdkLoggingConfigured } = await loadMatrixAuthClientDeps();
136
+ ensureMatrixSdkLoggingConfigured();
137
+ const tempClient = new MatrixClient(params.homeserver, params.accessToken, {
138
+ userId: params.userId,
139
+ ssrfPolicy: params.ssrfPolicy,
140
+ dispatcherPolicy: params.dispatcherPolicy,
141
+ });
142
+ return (await retryMatrixAuthRequest("matrix auth whoami", async () => {
143
+ return (await tempClient.doRequest("GET", "/_matrix/client/v3/account/whoami")) as {
144
+ user_id?: string;
145
+ device_id?: string;
146
+ };
147
+ })) as {
148
+ user_id?: string;
149
+ device_id?: string;
150
+ };
151
+ }
152
+
153
+ function readEnvSecretRefFallback(params: {
154
+ value: unknown;
155
+ env?: NodeJS.ProcessEnv;
156
+ config?: Pick<CoreConfig, "secrets">;
157
+ }): string | undefined {
158
+ const ref = coerceSecretRef(params.value, params.config?.secrets?.defaults);
159
+ if (!ref || ref.source !== "env" || !params.env) {
160
+ return undefined;
161
+ }
162
+
163
+ const providerConfig = params.config?.secrets?.providers?.[ref.provider];
164
+ if (providerConfig) {
165
+ if (providerConfig.source !== "env") {
166
+ throw new Error(
167
+ `Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "env".`,
168
+ );
169
+ }
170
+ if (providerConfig.allowlist && !providerConfig.allowlist.includes(ref.id)) {
171
+ throw new Error(
172
+ `Environment variable "${ref.id}" is not allowlisted in secrets.providers.${ref.provider}.allowlist.`,
173
+ );
174
+ }
175
+ } else if (ref.provider !== (params.config?.secrets?.defaults?.env?.trim() || "default")) {
176
+ throw new Error(
177
+ `Secret provider "${ref.provider}" is not configured (ref: ${ref.source}:${ref.provider}:${ref.id}).`,
178
+ );
179
+ }
180
+
181
+ const resolved = params.env[ref.id];
182
+ if (typeof resolved !== "string") {
183
+ return undefined;
184
+ }
185
+
186
+ const trimmed = resolved.trim();
187
+ return trimmed.length > 0 ? trimmed : undefined;
188
+ }
189
+
190
+ function clean(
191
+ value: unknown,
192
+ path: string,
193
+ opts?: {
194
+ env?: NodeJS.ProcessEnv;
195
+ config?: Pick<CoreConfig, "secrets">;
196
+ allowEnvSecretRefFallback?: boolean;
197
+ suppressSecretRef?: boolean;
198
+ },
199
+ ): string {
200
+ const ref = coerceSecretRef(value, opts?.config?.secrets?.defaults);
201
+ if (opts?.suppressSecretRef && ref) {
202
+ return "";
203
+ }
204
+ const normalizedValue = opts?.allowEnvSecretRefFallback
205
+ ? ref?.source === "env"
206
+ ? (readEnvSecretRefFallback({
207
+ value,
208
+ env: opts.env,
209
+ config: opts.config,
210
+ }) ?? value)
211
+ : ref
212
+ ? ""
213
+ : value
214
+ : value;
215
+ return (
216
+ normalizeResolvedSecretInputString({
217
+ value: normalizedValue,
218
+ path,
219
+ defaults: opts?.config?.secrets?.defaults,
220
+ }) ?? ""
221
+ );
222
+ }
223
+
224
+ type MatrixEnvConfig = {
225
+ homeserver: string;
226
+ userId: string;
227
+ accessToken?: string;
228
+ password?: string;
229
+ deviceId?: string;
230
+ deviceName?: string;
231
+ };
232
+
233
+ type MatrixConfigStringField =
234
+ | "homeserver"
235
+ | "userId"
236
+ | "accessToken"
237
+ | "password"
238
+ | "deviceId"
239
+ | "deviceName";
240
+
241
+ function resolveMatrixBaseConfigFieldPath(field: MatrixConfigStringField): string {
242
+ return `channels.lobi.${field}`;
243
+ }
244
+
245
+ function shouldAllowEnvSecretRefFallback(field: MatrixConfigStringField): boolean {
246
+ return field === "accessToken" || field === "password";
247
+ }
248
+
249
+ type MatrixAuthSecretField = "accessToken" | "password";
250
+
251
+ type MatrixConfiguredAuthInput = {
252
+ value: unknown;
253
+ path: string;
254
+ };
255
+
256
+ function hasConfiguredSecretInputValue(value: unknown, cfg: Pick<CoreConfig, "secrets">): boolean {
257
+ return (
258
+ (typeof value === "string" && value.trim().length > 0) ||
259
+ Boolean(coerceSecretRef(value, cfg.secrets?.defaults))
260
+ );
261
+ }
262
+
263
+ function hasConfiguredMatrixAccessTokenSource(params: {
264
+ cfg: CoreConfig;
265
+ env: NodeJS.ProcessEnv;
266
+ accountId: string;
267
+ }): boolean {
268
+ const normalizedAccountId = normalizeAccountId(params.accountId);
269
+ const account = findMatrixAccountConfig(params.cfg, normalizedAccountId) ?? {};
270
+ const scopedAccessTokenVar = getMatrixScopedEnvVarNames(normalizedAccountId).accessToken;
271
+ if (
272
+ hasConfiguredSecretInputValue(account.accessToken, params.cfg) ||
273
+ clean(params.env[scopedAccessTokenVar], scopedAccessTokenVar).length > 0
274
+ ) {
275
+ return true;
276
+ }
277
+ if (normalizedAccountId !== DEFAULT_ACCOUNT_ID) {
278
+ return false;
279
+ }
280
+ const matrix = resolveMatrixBaseConfig(params.cfg);
281
+ return (
282
+ hasConfiguredSecretInputValue(matrix.accessToken, params.cfg) ||
283
+ clean(params.env.LOBI_ACCESS_TOKEN, "LOBI_ACCESS_TOKEN").length > 0
284
+ );
285
+ }
286
+
287
+ function resolveConfiguredMatrixAuthInput(params: {
288
+ cfg: CoreConfig;
289
+ env: NodeJS.ProcessEnv;
290
+ accountId: string;
291
+ field: MatrixAuthSecretField;
292
+ }): MatrixConfiguredAuthInput | undefined {
293
+ const normalizedAccountId = normalizeAccountId(params.accountId);
294
+ const account = findMatrixAccountConfig(params.cfg, normalizedAccountId) ?? {};
295
+ const accountValue = account[params.field];
296
+ if (accountValue !== undefined) {
297
+ return {
298
+ value: accountValue,
299
+ path: resolveMatrixConfigFieldPath(params.cfg, normalizedAccountId, params.field),
300
+ };
301
+ }
302
+
303
+ const scopedKeys = getMatrixScopedEnvVarNames(normalizedAccountId);
304
+ const scopedEnv = resolveScopedMatrixEnvConfig(normalizedAccountId, params.env);
305
+ const scopedValue = scopedEnv[params.field];
306
+ if (scopedValue !== undefined) {
307
+ return {
308
+ value: scopedValue,
309
+ path: params.field === "accessToken" ? scopedKeys.accessToken : scopedKeys.password,
310
+ };
311
+ }
312
+
313
+ if (normalizedAccountId !== DEFAULT_ACCOUNT_ID) {
314
+ return undefined;
315
+ }
316
+
317
+ const matrix = resolveMatrixBaseConfig(params.cfg);
318
+ const baseValue = matrix[params.field];
319
+ if (baseValue !== undefined) {
320
+ return {
321
+ value: baseValue,
322
+ path: resolveMatrixBaseConfigFieldPath(params.field),
323
+ };
324
+ }
325
+
326
+ const globalValue =
327
+ params.field === "accessToken" ? params.env.LOBI_ACCESS_TOKEN : params.env.LOBI_PASSWORD;
328
+ if (globalValue !== undefined) {
329
+ return {
330
+ value: globalValue,
331
+ path: params.field === "accessToken" ? "LOBI_ACCESS_TOKEN" : "LOBI_PASSWORD",
332
+ };
333
+ }
334
+
335
+ return undefined;
336
+ }
337
+
338
+ async function resolveConfiguredMatrixAuthSecretInput(params: {
339
+ cfg: CoreConfig;
340
+ env: NodeJS.ProcessEnv;
341
+ accountId: string;
342
+ field: MatrixAuthSecretField;
343
+ }): Promise<string | undefined> {
344
+ const configured = resolveConfiguredMatrixAuthInput(params);
345
+ if (!configured) {
346
+ return undefined;
347
+ }
348
+
349
+ const { resolveConfiguredSecretInputString } = await loadMatrixSecretInputDeps();
350
+ const resolved = await resolveConfiguredSecretInputString({
351
+ config: params.cfg,
352
+ env: params.env,
353
+ value: configured.value,
354
+ path: configured.path,
355
+ unresolvedReasonStyle: "detailed",
356
+ });
357
+ if (resolved.value !== undefined) {
358
+ return resolved.value;
359
+ }
360
+
361
+ if (coerceSecretRef(configured.value, params.cfg.secrets?.defaults)) {
362
+ throw new Error(
363
+ resolved.unresolvedRefReason ?? `${configured.path} SecretRef could not be resolved.`,
364
+ );
365
+ }
366
+
367
+ return undefined;
368
+ }
369
+
370
+ function readMatrixBaseConfigField(
371
+ matrix: ReturnType<typeof resolveMatrixBaseConfig>,
372
+ field: MatrixConfigStringField,
373
+ opts?: {
374
+ env?: NodeJS.ProcessEnv;
375
+ config?: Pick<CoreConfig, "secrets">;
376
+ suppressSecretRef?: boolean;
377
+ },
378
+ ): string {
379
+ return clean(matrix[field], resolveMatrixBaseConfigFieldPath(field), {
380
+ env: opts?.env,
381
+ config: opts?.config,
382
+ allowEnvSecretRefFallback: shouldAllowEnvSecretRefFallback(field),
383
+ suppressSecretRef: opts?.suppressSecretRef,
384
+ });
385
+ }
386
+
387
+ function readMatrixAccountConfigField(
388
+ cfg: CoreConfig,
389
+ accountId: string,
390
+ account: Partial<Record<MatrixConfigStringField, unknown>>,
391
+ field: MatrixConfigStringField,
392
+ opts?: {
393
+ env?: NodeJS.ProcessEnv;
394
+ config?: Pick<CoreConfig, "secrets">;
395
+ suppressSecretRef?: boolean;
396
+ },
397
+ ): string {
398
+ return clean(account[field], resolveMatrixConfigFieldPath(cfg, accountId, field), {
399
+ env: opts?.env,
400
+ config: opts?.config,
401
+ allowEnvSecretRefFallback: shouldAllowEnvSecretRefFallback(field),
402
+ suppressSecretRef: opts?.suppressSecretRef,
403
+ });
404
+ }
405
+
406
+ function clampMatrixInitialSyncLimit(value: unknown): number | undefined {
407
+ return typeof value === "number" ? Math.max(0, Math.floor(value)) : undefined;
408
+ }
409
+
410
+ const MATRIX_HTTP_HOMESERVER_ERROR =
411
+ "Matrix homeserver must use https:// unless it targets a private or loopback host";
412
+
413
+ function buildMatrixNetworkFields(params: {
414
+ allowPrivateNetwork: boolean | undefined;
415
+ proxy?: string;
416
+ dispatcherPolicy?: PinnedDispatcherPolicy;
417
+ }): Pick<MatrixResolvedConfig, "allowPrivateNetwork" | "ssrfPolicy" | "dispatcherPolicy"> {
418
+ const dispatcherPolicy: PinnedDispatcherPolicy | undefined =
419
+ params.dispatcherPolicy ??
420
+ (params.proxy ? { mode: "explicit-proxy", proxyUrl: params.proxy } : undefined);
421
+ if (!params.allowPrivateNetwork && !dispatcherPolicy) {
422
+ return {};
423
+ }
424
+ return {
425
+ ...(params.allowPrivateNetwork
426
+ ? {
427
+ allowPrivateNetwork: true,
428
+ ssrfPolicy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(true),
429
+ }
430
+ : {}),
431
+ ...(dispatcherPolicy ? { dispatcherPolicy } : {}),
432
+ };
433
+ }
434
+
435
+ const DEFAULT_LOBI_HOMESERVER = "https://lobi.lobisland.com/";
436
+
437
+ export function resolveGlobalMatrixEnvConfig(env: NodeJS.ProcessEnv): MatrixEnvConfig {
438
+ const homeserver = clean(env.LOBI_HOMESERVER, "LOBI_HOMESERVER");
439
+ return {
440
+ homeserver: homeserver || DEFAULT_LOBI_HOMESERVER,
441
+ userId: clean(env.LOBI_USER_ID, "LOBI_USER_ID"),
442
+ accessToken: clean(env.LOBI_ACCESS_TOKEN, "LOBI_ACCESS_TOKEN") || undefined,
443
+ password: clean(env.LOBI_PASSWORD, "LOBI_PASSWORD") || undefined,
444
+ deviceId: clean(env.LOBI_DEVICE_ID, "LOBI_DEVICE_ID") || undefined,
445
+ deviceName: clean(env.LOBI_DEVICE_NAME, "LOBI_DEVICE_NAME") || undefined,
446
+ };
447
+ }
448
+
449
+ export { getLobiScopedEnvVarNames as getMatrixScopedEnvVarNames } from "../../env-vars.js";
450
+
451
+ export function resolveMatrixEnvAuthReadiness(
452
+ accountId: string,
453
+ env: NodeJS.ProcessEnv = process.env,
454
+ ): {
455
+ ready: boolean;
456
+ homeserver?: string;
457
+ userId?: string;
458
+ sourceHint: string;
459
+ missingMessage: string;
460
+ } {
461
+ const normalizedAccountId = normalizeAccountId(accountId);
462
+ const scoped = resolveScopedMatrixEnvConfig(normalizedAccountId, env);
463
+ const scopedReady = hasReadyMatrixEnvAuth(scoped);
464
+ if (normalizedAccountId !== DEFAULT_ACCOUNT_ID) {
465
+ const keys = getMatrixScopedEnvVarNames(normalizedAccountId);
466
+ return {
467
+ ready: scopedReady,
468
+ homeserver: scoped.homeserver || undefined,
469
+ userId: scoped.userId || undefined,
470
+ sourceHint: `${keys.homeserver} (+ auth vars)`,
471
+ missingMessage: `Set per-account env vars for "${normalizedAccountId}" (for example ${keys.homeserver} + ${keys.accessToken} or ${keys.userId} + ${keys.password}).`,
472
+ };
473
+ }
474
+
475
+ const defaultScoped = resolveScopedMatrixEnvConfig(DEFAULT_ACCOUNT_ID, env);
476
+ const global = resolveGlobalMatrixEnvConfig(env);
477
+ const defaultScopedReady = hasReadyMatrixEnvAuth(defaultScoped);
478
+ const globalReady = hasReadyMatrixEnvAuth(global);
479
+ const defaultKeys = getMatrixScopedEnvVarNames(DEFAULT_ACCOUNT_ID);
480
+ return {
481
+ ready: defaultScopedReady || globalReady,
482
+ homeserver: defaultScoped.homeserver || global.homeserver || undefined,
483
+ userId: defaultScoped.userId || global.userId || undefined,
484
+ sourceHint: "LOBI_* or LOBI_DEFAULT_*",
485
+ missingMessage:
486
+ `Set Lobi env vars for the default account ` +
487
+ `(for example LOBI_HOMESERVER + LOBI_ACCESS_TOKEN, LOBI_USER_ID + LOBI_PASSWORD, ` +
488
+ `or ${defaultKeys.homeserver} + ${defaultKeys.accessToken}).`,
489
+ };
490
+ }
491
+
492
+ export function resolveScopedMatrixEnvConfig(
493
+ accountId: string,
494
+ env: NodeJS.ProcessEnv = process.env,
495
+ ): MatrixEnvConfig {
496
+ const keys = getMatrixScopedEnvVarNames(accountId);
497
+ return {
498
+ homeserver: clean(env[keys.homeserver], keys.homeserver),
499
+ userId: clean(env[keys.userId], keys.userId),
500
+ accessToken: clean(env[keys.accessToken], keys.accessToken) || undefined,
501
+ password: clean(env[keys.password], keys.password) || undefined,
502
+ deviceId: clean(env[keys.deviceId], keys.deviceId) || undefined,
503
+ deviceName: clean(env[keys.deviceName], keys.deviceName) || undefined,
504
+ };
505
+ }
506
+
507
+ function hasScopedMatrixEnvConfig(accountId: string, env: NodeJS.ProcessEnv): boolean {
508
+ const scoped = resolveScopedMatrixEnvConfig(accountId, env);
509
+ return Boolean(
510
+ scoped.homeserver ||
511
+ scoped.userId ||
512
+ scoped.accessToken ||
513
+ scoped.password ||
514
+ scoped.deviceId ||
515
+ scoped.deviceName,
516
+ );
517
+ }
518
+
519
+ export function hasReadyMatrixEnvAuth(config: {
520
+ homeserver?: string;
521
+ userId?: string;
522
+ accessToken?: string;
523
+ password?: string;
524
+ }): boolean {
525
+ const homeserver = clean(config.homeserver, "matrix.env.homeserver");
526
+ const userId = clean(config.userId, "matrix.env.userId");
527
+ const accessToken = clean(config.accessToken, "matrix.env.accessToken");
528
+ const password = clean(config.password, "matrix.env.password");
529
+ return Boolean(homeserver && (accessToken || (userId && password)));
530
+ }
531
+
532
+ export function validateMatrixHomeserverUrl(
533
+ homeserver: string,
534
+ opts?: { allowPrivateNetwork?: boolean },
535
+ ): string {
536
+ const trimmed = clean(homeserver, "matrix.homeserver");
537
+ if (!trimmed) {
538
+ throw new Error("Matrix homeserver is required (matrix.homeserver)");
539
+ }
540
+
541
+ let parsed: URL;
542
+ try {
543
+ parsed = new URL(trimmed);
544
+ } catch {
545
+ throw new Error("Matrix homeserver must be a valid http(s) URL");
546
+ }
547
+
548
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
549
+ throw new Error("Matrix homeserver must use http:// or https://");
550
+ }
551
+ if (!parsed.hostname) {
552
+ throw new Error("Matrix homeserver must include a hostname");
553
+ }
554
+ if (parsed.username || parsed.password) {
555
+ throw new Error("Matrix homeserver URL must not include embedded credentials");
556
+ }
557
+ if (parsed.search || parsed.hash) {
558
+ throw new Error("Matrix homeserver URL must not include query strings or fragments");
559
+ }
560
+ if (
561
+ parsed.protocol === "http:" &&
562
+ opts?.allowPrivateNetwork !== true &&
563
+ !isPrivateOrLoopbackHost(parsed.hostname)
564
+ ) {
565
+ throw new Error(MATRIX_HTTP_HOMESERVER_ERROR);
566
+ }
567
+
568
+ return trimmed;
569
+ }
570
+
571
+ export async function resolveValidatedMatrixHomeserverUrl(
572
+ homeserver: string,
573
+ opts?: {
574
+ dangerouslyAllowPrivateNetwork?: boolean;
575
+ allowPrivateNetwork?: boolean;
576
+ lookupFn?: LookupFn;
577
+ },
578
+ ): Promise<string> {
579
+ const allowPrivateNetwork =
580
+ typeof opts?.dangerouslyAllowPrivateNetwork === "boolean"
581
+ ? opts.dangerouslyAllowPrivateNetwork
582
+ : opts?.allowPrivateNetwork;
583
+ const normalized = validateMatrixHomeserverUrl(homeserver, {
584
+ allowPrivateNetwork,
585
+ });
586
+ await assertHttpUrlTargetsPrivateNetwork(normalized, {
587
+ dangerouslyAllowPrivateNetwork: opts?.dangerouslyAllowPrivateNetwork,
588
+ allowPrivateNetwork,
589
+ lookupFn: opts?.lookupFn,
590
+ errorMessage: MATRIX_HTTP_HOMESERVER_ERROR,
591
+ });
592
+ return normalized;
593
+ }
594
+
595
+ export function resolveMatrixConfigForAccount(
596
+ cfg: CoreConfig,
597
+ accountId: string,
598
+ env: NodeJS.ProcessEnv = process.env,
599
+ ): MatrixResolvedConfig {
600
+ const matrix = resolveMatrixBaseConfig(cfg);
601
+ const account = findMatrixAccountConfig(cfg, accountId) ?? {};
602
+ const normalizedAccountId = normalizeAccountId(accountId);
603
+ const suppressInactivePasswordSecretRef = hasConfiguredMatrixAccessTokenSource({
604
+ cfg,
605
+ env,
606
+ accountId: normalizedAccountId,
607
+ });
608
+ const fieldReadOptions = {
609
+ env,
610
+ config: cfg,
611
+ };
612
+ const scopedEnv = resolveScopedMatrixEnvConfig(normalizedAccountId, env);
613
+ const globalEnv = resolveGlobalMatrixEnvConfig(env);
614
+ const accountField = (field: MatrixConfigStringField) =>
615
+ readMatrixAccountConfigField(cfg, normalizedAccountId, account, field, {
616
+ ...fieldReadOptions,
617
+ suppressSecretRef: field === "password" ? suppressInactivePasswordSecretRef : undefined,
618
+ });
619
+ const resolvedStrings = resolveMatrixAccountStringValues({
620
+ accountId: normalizedAccountId,
621
+ account: {
622
+ homeserver: accountField("homeserver"),
623
+ userId: accountField("userId"),
624
+ accessToken: accountField("accessToken"),
625
+ password: accountField("password"),
626
+ deviceId: accountField("deviceId"),
627
+ deviceName: accountField("deviceName"),
628
+ },
629
+ scopedEnv,
630
+ channel: {
631
+ homeserver: readMatrixBaseConfigField(matrix, "homeserver", fieldReadOptions),
632
+ userId: readMatrixBaseConfigField(matrix, "userId", fieldReadOptions),
633
+ accessToken: readMatrixBaseConfigField(matrix, "accessToken", fieldReadOptions),
634
+ password: readMatrixBaseConfigField(matrix, "password", {
635
+ ...fieldReadOptions,
636
+ suppressSecretRef: suppressInactivePasswordSecretRef,
637
+ }),
638
+ deviceId: readMatrixBaseConfigField(matrix, "deviceId", fieldReadOptions),
639
+ deviceName: readMatrixBaseConfigField(matrix, "deviceName", fieldReadOptions),
640
+ },
641
+ globalEnv,
642
+ });
643
+
644
+ const accountInitialSyncLimit = clampMatrixInitialSyncLimit(account.initialSyncLimit);
645
+ const initialSyncLimit =
646
+ accountInitialSyncLimit ?? clampMatrixInitialSyncLimit(matrix.initialSyncLimit);
647
+ const encryption =
648
+ typeof account.encryption === "boolean" ? account.encryption : (matrix.encryption ?? false);
649
+ const allowPrivateNetwork =
650
+ isPrivateNetworkOptInEnabled(account) || isPrivateNetworkOptInEnabled(matrix)
651
+ ? true
652
+ : undefined;
653
+
654
+ return {
655
+ homeserver: resolvedStrings.homeserver,
656
+ userId: resolvedStrings.userId,
657
+ accessToken: resolvedStrings.accessToken || undefined,
658
+ password: resolvedStrings.password || undefined,
659
+ deviceId: resolvedStrings.deviceId || undefined,
660
+ deviceName: resolvedStrings.deviceName || undefined,
661
+ initialSyncLimit,
662
+ encryption,
663
+ ...buildMatrixNetworkFields({
664
+ allowPrivateNetwork,
665
+ proxy: account.proxy ?? matrix.proxy,
666
+ }),
667
+ };
668
+ }
669
+
670
+ function resolveImplicitMatrixAccountId(
671
+ cfg: CoreConfig,
672
+ env: NodeJS.ProcessEnv = process.env,
673
+ ): string | null {
674
+ if (requiresExplicitMatrixDefaultAccount(cfg, env)) {
675
+ return null;
676
+ }
677
+ return normalizeAccountId(resolveMatrixDefaultOrOnlyAccountId(cfg, env));
678
+ }
679
+
680
+ export function resolveMatrixAuthContext(params?: {
681
+ cfg?: CoreConfig;
682
+ env?: NodeJS.ProcessEnv;
683
+ accountId?: string | null;
684
+ }): {
685
+ cfg: CoreConfig;
686
+ env: NodeJS.ProcessEnv;
687
+ accountId: string;
688
+ resolved: MatrixResolvedConfig;
689
+ } {
690
+ const cfg = params?.cfg ?? (getMatrixRuntime().config.loadConfig() as CoreConfig);
691
+ const env = params?.env ?? process.env;
692
+ const explicitAccountId = normalizeOptionalAccountId(params?.accountId);
693
+ const effectiveAccountId = explicitAccountId ?? resolveImplicitMatrixAccountId(cfg, env);
694
+ if (!effectiveAccountId) {
695
+ throw new Error(
696
+ 'Multiple Lobi accounts are configured and channels.lobi.defaultAccount is not set. Set "channels.lobi.defaultAccount" to the intended account or pass --account <id>.',
697
+ );
698
+ }
699
+ if (
700
+ explicitAccountId &&
701
+ explicitAccountId !== DEFAULT_ACCOUNT_ID &&
702
+ !listNormalizedMatrixAccountIds(cfg).includes(explicitAccountId) &&
703
+ !hasScopedMatrixEnvConfig(explicitAccountId, env)
704
+ ) {
705
+ throw new Error(
706
+ `Lobi account "${explicitAccountId}" is not configured. Add channels.lobi.accounts.${explicitAccountId} or define scoped ${getMatrixScopedEnvVarNames(explicitAccountId).accessToken.replace(/_ACCESS_TOKEN$/, "")}_* variables.`,
707
+ );
708
+ }
709
+ const resolved = resolveMatrixConfigForAccount(cfg, effectiveAccountId, env);
710
+
711
+ return {
712
+ cfg,
713
+ env,
714
+ accountId: effectiveAccountId,
715
+ resolved,
716
+ };
717
+ }
718
+
719
+ export async function resolveMatrixAuth(params?: {
720
+ cfg?: CoreConfig;
721
+ env?: NodeJS.ProcessEnv;
722
+ accountId?: string | null;
723
+ }): Promise<MatrixAuth> {
724
+ const { cfg, env, accountId, resolved } = resolveMatrixAuthContext(params);
725
+ const accessToken =
726
+ (await resolveConfiguredMatrixAuthSecretInput({
727
+ cfg,
728
+ env,
729
+ accountId,
730
+ field: "accessToken",
731
+ })) ?? resolved.accessToken;
732
+ const tokenAuthPassword = resolved.password;
733
+ const homeserver = await resolveValidatedMatrixHomeserverUrl(resolved.homeserver, {
734
+ dangerouslyAllowPrivateNetwork: resolved.allowPrivateNetwork,
735
+ });
736
+ let credentialsWriter: typeof import("../credentials-write.runtime.js") | undefined;
737
+ const loadCredentialsWriter = async () => {
738
+ credentialsWriter ??= await import("../credentials-write.runtime.js");
739
+ return credentialsWriter;
740
+ };
741
+
742
+ const { loadMatrixCredentials, credentialsMatchConfig } = await loadMatrixCredentialsReadDeps();
743
+ const cached = loadMatrixCredentials(env, accountId);
744
+ const cachedCredentials =
745
+ cached &&
746
+ credentialsMatchConfig(cached, {
747
+ homeserver,
748
+ userId: resolved.userId || "",
749
+ accessToken,
750
+ })
751
+ ? cached
752
+ : null;
753
+
754
+ // If we have an access token, we can fetch userId via whoami if not provided
755
+ if (accessToken) {
756
+ let userId = resolved.userId;
757
+ const hasMatchingCachedToken = cachedCredentials?.accessToken === accessToken;
758
+ let knownDeviceId = hasMatchingCachedToken
759
+ ? cachedCredentials?.deviceId || resolved.deviceId
760
+ : resolved.deviceId;
761
+
762
+ if (!userId) {
763
+ // Only block startup on whoami when token auth still needs the user ID.
764
+ // A missing device ID alone is optional and should not force a network round-trip.
765
+ const whoami = await fetchMatrixWhoamiIdentity({
766
+ homeserver,
767
+ accessToken,
768
+ userId,
769
+ ssrfPolicy: resolved.ssrfPolicy,
770
+ dispatcherPolicy: resolved.dispatcherPolicy,
771
+ });
772
+ const fetchedUserId = whoami.user_id?.trim();
773
+ if (!fetchedUserId) {
774
+ throw new Error("Matrix whoami did not return user_id");
775
+ }
776
+ userId = fetchedUserId;
777
+ knownDeviceId = knownDeviceId || whoami.device_id?.trim() || resolved.deviceId;
778
+ }
779
+
780
+ const shouldRefreshCachedCredentials =
781
+ !cachedCredentials ||
782
+ !hasMatchingCachedToken ||
783
+ cachedCredentials.userId !== userId ||
784
+ (cachedCredentials.deviceId || undefined) !== knownDeviceId;
785
+ if (shouldRefreshCachedCredentials) {
786
+ const { saveMatrixCredentials } = await loadCredentialsWriter();
787
+ await saveMatrixCredentials(
788
+ {
789
+ homeserver,
790
+ userId,
791
+ accessToken,
792
+ deviceId: knownDeviceId,
793
+ },
794
+ env,
795
+ accountId,
796
+ );
797
+ } else if (hasMatchingCachedToken) {
798
+ const { touchMatrixCredentials } = await loadCredentialsWriter();
799
+ await touchMatrixCredentials(env, accountId);
800
+ }
801
+ return {
802
+ accountId,
803
+ homeserver,
804
+ userId,
805
+ accessToken,
806
+ password: tokenAuthPassword,
807
+ deviceId: knownDeviceId,
808
+ deviceName: resolved.deviceName,
809
+ initialSyncLimit: resolved.initialSyncLimit,
810
+ encryption: resolved.encryption,
811
+ ...buildMatrixNetworkFields({
812
+ allowPrivateNetwork: resolved.allowPrivateNetwork,
813
+ dispatcherPolicy: resolved.dispatcherPolicy,
814
+ }),
815
+ };
816
+ }
817
+
818
+ if (cachedCredentials) {
819
+ const { touchMatrixCredentials } = await loadCredentialsWriter();
820
+ await touchMatrixCredentials(env, accountId);
821
+ return {
822
+ accountId,
823
+ homeserver: cachedCredentials.homeserver,
824
+ userId: cachedCredentials.userId,
825
+ accessToken: cachedCredentials.accessToken,
826
+ password: tokenAuthPassword,
827
+ deviceId: cachedCredentials.deviceId || resolved.deviceId,
828
+ deviceName: resolved.deviceName,
829
+ initialSyncLimit: resolved.initialSyncLimit,
830
+ encryption: resolved.encryption,
831
+ ...buildMatrixNetworkFields({
832
+ allowPrivateNetwork: resolved.allowPrivateNetwork,
833
+ dispatcherPolicy: resolved.dispatcherPolicy,
834
+ }),
835
+ };
836
+ }
837
+
838
+ if (!resolved.userId) {
839
+ throw new Error("Matrix userId is required when no access token is configured (matrix.userId)");
840
+ }
841
+
842
+ const password =
843
+ (await resolveConfiguredMatrixAuthSecretInput({
844
+ cfg,
845
+ env,
846
+ accountId,
847
+ field: "password",
848
+ })) ?? resolved.password;
849
+ if (!password) {
850
+ throw new Error(
851
+ "Matrix password is required when no access token is configured (matrix.password)",
852
+ );
853
+ }
854
+
855
+ // Login with password using the same hardened request path as other Matrix HTTP calls.
856
+ const { MatrixClient, ensureMatrixSdkLoggingConfigured } = await loadMatrixAuthClientDeps();
857
+ ensureMatrixSdkLoggingConfigured();
858
+ const loginClient = new MatrixClient(homeserver, "", {
859
+ ssrfPolicy: resolved.ssrfPolicy,
860
+ dispatcherPolicy: resolved.dispatcherPolicy,
861
+ });
862
+ const login = (await retryMatrixAuthRequest("matrix auth login", async () => {
863
+ return (await loginClient.doRequest("POST", "/_matrix/client/v3/login", undefined, {
864
+ type: "m.login.password",
865
+ identifier: { type: "m.id.user", user: resolved.userId },
866
+ password,
867
+ device_id: resolved.deviceId,
868
+ initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway",
869
+ })) as {
870
+ access_token?: string;
871
+ user_id?: string;
872
+ device_id?: string;
873
+ };
874
+ })) as {
875
+ access_token?: string;
876
+ user_id?: string;
877
+ device_id?: string;
878
+ };
879
+
880
+ const loginAccessToken = login.access_token?.trim();
881
+ if (!loginAccessToken) {
882
+ throw new Error("Matrix login did not return an access token");
883
+ }
884
+
885
+ const auth: MatrixAuth = {
886
+ accountId,
887
+ homeserver,
888
+ userId: login.user_id ?? resolved.userId,
889
+ accessToken: loginAccessToken,
890
+ password,
891
+ deviceId: login.device_id ?? resolved.deviceId,
892
+ deviceName: resolved.deviceName,
893
+ initialSyncLimit: resolved.initialSyncLimit,
894
+ encryption: resolved.encryption,
895
+ ...buildMatrixNetworkFields({
896
+ allowPrivateNetwork: resolved.allowPrivateNetwork,
897
+ dispatcherPolicy: resolved.dispatcherPolicy,
898
+ }),
899
+ };
900
+
901
+ const { saveMatrixCredentials } = await loadCredentialsWriter();
902
+ await saveMatrixCredentials(
903
+ {
904
+ homeserver: auth.homeserver,
905
+ userId: auth.userId,
906
+ accessToken: auth.accessToken,
907
+ deviceId: auth.deviceId,
908
+ },
909
+ env,
910
+ accountId,
911
+ );
912
+
913
+ return auth;
914
+ }
915
+
916
+ export async function backfillMatrixAuthDeviceIdAfterStartup(params: {
917
+ auth: MatrixAuth;
918
+ env?: NodeJS.ProcessEnv;
919
+ abortSignal?: AbortSignal;
920
+ }): Promise<string | undefined> {
921
+ const knownDeviceId = params.auth.deviceId?.trim();
922
+ if (knownDeviceId) {
923
+ return knownDeviceId;
924
+ }
925
+ if (isAbortSignalTriggered(params.abortSignal)) {
926
+ return undefined;
927
+ }
928
+
929
+ const whoami = await fetchMatrixWhoamiIdentity({
930
+ homeserver: params.auth.homeserver,
931
+ accessToken: params.auth.accessToken,
932
+ userId: params.auth.userId,
933
+ ssrfPolicy: params.auth.ssrfPolicy,
934
+ dispatcherPolicy: params.auth.dispatcherPolicy,
935
+ });
936
+ const deviceId = whoami.device_id?.trim();
937
+ if (!deviceId) {
938
+ return undefined;
939
+ }
940
+ if (isAbortSignalTriggered(params.abortSignal)) {
941
+ return undefined;
942
+ }
943
+
944
+ const env = params.env ?? process.env;
945
+ const { loadMatrixCredentials } = await loadMatrixCredentialsReadDeps();
946
+ if (
947
+ !credentialsMatchBackfillAuthLineage({
948
+ stored: loadMatrixCredentials(env, params.auth.accountId),
949
+ auth: params.auth,
950
+ })
951
+ ) {
952
+ return undefined;
953
+ }
954
+
955
+ const repairedStorageMeta = repairCurrentTokenStorageMetaDeviceId({
956
+ homeserver: params.auth.homeserver,
957
+ userId: params.auth.userId,
958
+ accessToken: params.auth.accessToken,
959
+ accountId: params.auth.accountId,
960
+ deviceId,
961
+ env: params.env,
962
+ });
963
+ if (!repairedStorageMeta) {
964
+ throw new Error("Matrix deviceId backfill failed to repair current-token storage metadata");
965
+ }
966
+ if (isAbortSignalTriggered(params.abortSignal)) {
967
+ return undefined;
968
+ }
969
+
970
+ const credentialsWriter = await import("../credentials-write.runtime.js");
971
+ const saved = await credentialsWriter.saveBackfilledMatrixDeviceId(
972
+ {
973
+ homeserver: params.auth.homeserver,
974
+ userId: params.auth.userId,
975
+ accessToken: params.auth.accessToken,
976
+ deviceId,
977
+ },
978
+ env,
979
+ params.auth.accountId,
980
+ );
981
+ return saved === "saved" ? deviceId : undefined;
982
+ }