@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,340 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { markdownToMatrixHtml, renderMarkdownToMatrixHtmlWithMentions } from "./format.js";
3
+
4
+ function createMentionClient(selfUserId = "@bot:example.org") {
5
+ return {
6
+ getUserId: async () => selfUserId,
7
+ } as unknown as import("./sdk.js").MatrixClient;
8
+ }
9
+
10
+ describe("markdownToMatrixHtml", () => {
11
+ it("renders basic inline formatting", () => {
12
+ const html = markdownToMatrixHtml("hi _there_ **boss** `code`");
13
+ expect(html).toContain("<em>there</em>");
14
+ expect(html).toContain("<strong>boss</strong>");
15
+ expect(html).toContain("<code>code</code>");
16
+ });
17
+
18
+ it("renders links as HTML", () => {
19
+ const html = markdownToMatrixHtml("see [docs](https://example.com)");
20
+ expect(html).toContain('<a href="https://example.com">docs</a>');
21
+ });
22
+
23
+ it("does not auto-link bare file references into external urls", () => {
24
+ const html = markdownToMatrixHtml("Check README.md and backup.sh");
25
+ expect(html).toContain("README.md");
26
+ expect(html).toContain("backup.sh");
27
+ expect(html).not.toContain('href="http://README.md"');
28
+ expect(html).not.toContain('href="http://backup.sh"');
29
+ });
30
+
31
+ it("keeps real domains linked even when path segments look like filenames", () => {
32
+ const html = markdownToMatrixHtml("See https://docs.example.com/backup.sh");
33
+ expect(html).toContain('href="https://docs.example.com/backup.sh"');
34
+ });
35
+
36
+ it("escapes raw HTML", () => {
37
+ const html = markdownToMatrixHtml("<b>nope</b>");
38
+ expect(html).toContain("&lt;b&gt;nope&lt;/b&gt;");
39
+ expect(html).not.toContain("<b>nope</b>");
40
+ });
41
+
42
+ it("flattens images into alt text", () => {
43
+ const html = markdownToMatrixHtml("![alt](https://example.com/img.png)");
44
+ expect(html).toContain("alt");
45
+ expect(html).not.toContain("<img");
46
+ });
47
+
48
+ it("preserves line breaks", () => {
49
+ const html = markdownToMatrixHtml("line1\nline2");
50
+ expect(html).toContain("<br");
51
+ });
52
+
53
+ it("compacts loose ordered lists without paragraph tags", () => {
54
+ const html = markdownToMatrixHtml("1. first\n\n2. second\n\n3. third");
55
+ expect(html).toContain("<ol>");
56
+ expect(html).toContain("<li>");
57
+ expect(html).not.toContain("<p>");
58
+ });
59
+
60
+ it("compacts loose unordered lists without paragraph tags", () => {
61
+ const html = markdownToMatrixHtml("- one\n\n- two\n\n- three");
62
+ expect(html).toContain("<ul>");
63
+ expect(html).not.toContain("<p>");
64
+ });
65
+
66
+ it("keeps tight lists unchanged", () => {
67
+ const html = markdownToMatrixHtml("- one\n- two");
68
+ expect(html).toContain("<ul>");
69
+ expect(html).not.toContain("<p>");
70
+ });
71
+
72
+ it("preserves inline formatting in loose lists", () => {
73
+ const html = markdownToMatrixHtml("1. **bold**\n\n2. _italic_");
74
+ expect(html).toContain("<strong>bold</strong>");
75
+ expect(html).toContain("<em>italic</em>");
76
+ expect(html).not.toContain("<p>");
77
+ });
78
+
79
+ it("does not strip paragraph tags outside lists", () => {
80
+ const html = markdownToMatrixHtml("Hello\n\nWorld");
81
+ expect(html).toContain("<p>Hello</p>");
82
+ expect(html).toContain("<p>World</p>");
83
+ });
84
+
85
+ it("compacts nested sublists without paragraph tags", () => {
86
+ const html = markdownToMatrixHtml("1. parent\n\n - child\n\n2. other");
87
+ expect(html).toContain("<ol>");
88
+ expect(html).toContain("<ul>");
89
+ expect(html).not.toContain("<p>");
90
+ });
91
+
92
+ it("compacts loose lists with mentions via renderMarkdownToMatrixHtmlWithMentions", async () => {
93
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
94
+ markdown: "1. hello @alice:example.org\n\n2. bye",
95
+ client: createMentionClient(),
96
+ });
97
+ expect(result.html).not.toContain("<p>");
98
+ expect(result.html).toContain('href="https://matrix.to/#/%40alice%3Aexample.org"');
99
+ expect(result.mentions).toEqual({ user_ids: ["@alice:example.org"] });
100
+ });
101
+
102
+ it("preserves paragraph wrappers for multi-paragraph list items", () => {
103
+ const html = markdownToMatrixHtml("1. First sentence.\n\n Second sentence in the same item.");
104
+ expect(html).toContain("<li>");
105
+ expect(html).toContain("<p>First sentence.</p>");
106
+ expect(html).toContain("<p>Second sentence in the same item.</p>");
107
+ });
108
+
109
+ it("renders qualified Matrix user mentions as matrix.to links and m.mentions metadata", async () => {
110
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
111
+ markdown: "hello @alice:example.org",
112
+ client: createMentionClient(),
113
+ });
114
+
115
+ expect(result.html).toContain('href="https://matrix.to/#/%40alice%3Aexample.org"');
116
+ expect(result.mentions).toEqual({
117
+ user_ids: ["@alice:example.org"],
118
+ });
119
+ });
120
+
121
+ it("url-encodes matrix.to hrefs for valid mxids with path characters", async () => {
122
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
123
+ markdown: "hello @foo/bar:example.org",
124
+ client: createMentionClient(),
125
+ });
126
+
127
+ expect(result.html).toContain('href="https://matrix.to/#/%40foo%2Fbar%3Aexample.org"');
128
+ expect(result.mentions).toEqual({
129
+ user_ids: ["@foo/bar:example.org"],
130
+ });
131
+ });
132
+
133
+ it("treats mxids that begin with room as user mentions", async () => {
134
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
135
+ markdown: "hello @room:example.org",
136
+ client: createMentionClient(),
137
+ });
138
+
139
+ expect(result.html).toContain('href="https://matrix.to/#/%40room%3Aexample.org"');
140
+ expect(result.mentions).toEqual({
141
+ user_ids: ["@room:example.org"],
142
+ });
143
+ });
144
+
145
+ it("treats hyphenated room-prefixed mxids as user mentions", async () => {
146
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
147
+ markdown: "hello @room-admin:example.org",
148
+ client: createMentionClient(),
149
+ });
150
+
151
+ expect(result.html).toContain('href="https://matrix.to/#/%40room-admin%3Aexample.org"');
152
+ expect(result.mentions).toEqual({
153
+ user_ids: ["@room-admin:example.org"],
154
+ });
155
+ });
156
+
157
+ it("keeps explicit room mentions as room mentions", async () => {
158
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
159
+ markdown: "hello @room",
160
+ client: createMentionClient(),
161
+ });
162
+
163
+ expect(result.html).toContain("@room");
164
+ expect(result.html).not.toContain("matrix.to");
165
+ expect(result.mentions).toEqual({
166
+ room: true,
167
+ });
168
+ });
169
+
170
+ it("treats sentence-ending room mentions as room mentions", async () => {
171
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
172
+ markdown: "hello @room.",
173
+ client: createMentionClient(),
174
+ });
175
+
176
+ expect(result.html).toContain("hello @room.");
177
+ expect(result.html).not.toContain("matrix.to");
178
+ expect(result.mentions).toEqual({
179
+ room: true,
180
+ });
181
+ });
182
+
183
+ it("treats colon-suffixed room mentions as room mentions", async () => {
184
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
185
+ markdown: "hello @room:",
186
+ client: createMentionClient(),
187
+ });
188
+
189
+ expect(result.html).toContain("hello @room:");
190
+ expect(result.html).not.toContain("matrix.to");
191
+ expect(result.mentions).toEqual({
192
+ room: true,
193
+ });
194
+ });
195
+
196
+ it("trims punctuation before storing mentioned user ids", async () => {
197
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
198
+ markdown: "hello @alice:example.org.",
199
+ client: createMentionClient(),
200
+ });
201
+
202
+ expect(result.html).toContain('href="https://matrix.to/#/%40alice%3Aexample.org"');
203
+ expect(result.html).toContain("@alice:example.org</a>.");
204
+ expect(result.mentions).toEqual({
205
+ user_ids: ["@alice:example.org"],
206
+ });
207
+ });
208
+
209
+ it("does not emit mentions for mxid-like tokens with path suffixes", async () => {
210
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
211
+ markdown: "hello @alice:example.org/path",
212
+ client: createMentionClient(),
213
+ });
214
+
215
+ expect(result.html).toContain("@alice:example.org/path");
216
+ expect(result.html).not.toContain("matrix.to");
217
+ expect(result.mentions).toEqual({});
218
+ });
219
+
220
+ it("accepts bracketed homeservers in matrix mentions", async () => {
221
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
222
+ markdown: "hello @alice:[2001:db8::1]",
223
+ client: createMentionClient(),
224
+ });
225
+
226
+ expect(result.html).toContain('href="https://matrix.to/#/%40alice%3A%5B2001%3Adb8%3A%3A1%5D"');
227
+ expect(result.mentions).toEqual({
228
+ user_ids: ["@alice:[2001:db8::1]"],
229
+ });
230
+ });
231
+
232
+ it("accepts bracketed homeservers with ports in matrix mentions", async () => {
233
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
234
+ markdown: "hello @alice:[2001:db8::1]:8448.",
235
+ client: createMentionClient(),
236
+ });
237
+
238
+ expect(result.html).toContain(
239
+ 'href="https://matrix.to/#/%40alice%3A%5B2001%3Adb8%3A%3A1%5D%3A8448"',
240
+ );
241
+ expect(result.html).toContain("@alice:[2001:db8::1]:8448</a>.");
242
+ expect(result.mentions).toEqual({
243
+ user_ids: ["@alice:[2001:db8::1]:8448"],
244
+ });
245
+ });
246
+
247
+ it("leaves bare localpart text unmentioned", async () => {
248
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
249
+ markdown: "hello @alice",
250
+ client: createMentionClient(),
251
+ });
252
+
253
+ expect(result.html).not.toContain("matrix.to");
254
+ expect(result.mentions).toEqual({});
255
+ });
256
+
257
+ it("does not convert escaped qualified mentions", async () => {
258
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
259
+ markdown: "\\@alice:example.org",
260
+ client: createMentionClient(),
261
+ });
262
+
263
+ expect(result.html).toContain("@alice:example.org");
264
+ expect(result.html).not.toContain("matrix.to");
265
+ expect(result.mentions).toEqual({});
266
+ });
267
+
268
+ it("does not convert escaped room mentions", async () => {
269
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
270
+ markdown: "\\@room",
271
+ client: createMentionClient(),
272
+ });
273
+
274
+ expect(result.html).toContain("@room");
275
+ expect(result.mentions).toEqual({});
276
+ });
277
+
278
+ it("keeps escaped mentions literal after escaped backticks", async () => {
279
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
280
+ markdown: "\\`literal then \\@alice:example.org",
281
+ client: createMentionClient(),
282
+ });
283
+
284
+ expect(result.html).toContain("`literal then @alice:example.org");
285
+ expect(result.html).not.toContain("matrix.to");
286
+ expect(result.mentions).toEqual({});
287
+ });
288
+
289
+ it("restores escaped mentions in markdown link labels without linking them", async () => {
290
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
291
+ markdown: "[\\@alice:example.org](https://example.com)",
292
+ client: createMentionClient(),
293
+ });
294
+
295
+ expect(result.html).toContain('<a href="https://example.com">@alice:example.org</a>');
296
+ expect(result.html).not.toContain("matrix.to");
297
+ expect(result.mentions).toEqual({});
298
+ });
299
+
300
+ it("keeps backslashes inside code spans", async () => {
301
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
302
+ markdown: "`\\@alice:example.org`",
303
+ client: createMentionClient(),
304
+ });
305
+
306
+ expect(result.html).toContain("<code>\\@alice:example.org</code>");
307
+ expect(result.mentions).toEqual({});
308
+ });
309
+
310
+ it("does not convert mentions inside code spans", async () => {
311
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
312
+ markdown: "`@alice:example.org`",
313
+ client: createMentionClient(),
314
+ });
315
+
316
+ expect(result.html).toContain("<code>@alice:example.org</code>");
317
+ expect(result.html).not.toContain("matrix.to");
318
+ expect(result.mentions).toEqual({});
319
+ });
320
+
321
+ it("keeps backslashes inside tilde fenced code blocks", async () => {
322
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
323
+ markdown: "~~~\n\\@alice:example.org\n~~~",
324
+ client: createMentionClient(),
325
+ });
326
+
327
+ expect(result.html).toContain("<pre><code>\\@alice:example.org\n</code></pre>");
328
+ expect(result.mentions).toEqual({});
329
+ });
330
+
331
+ it("keeps backslashes inside indented code blocks", async () => {
332
+ const result = await renderMarkdownToMatrixHtmlWithMentions({
333
+ markdown: " \\@alice:example.org",
334
+ client: createMentionClient(),
335
+ });
336
+
337
+ expect(result.html).toContain("<pre><code>\\@alice:example.org\n</code></pre>");
338
+ expect(result.mentions).toEqual({});
339
+ });
340
+ });