@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,178 @@
1
+ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { ChannelDirectoryEntry } from "../runtime-api.js";
3
+
4
+ vi.mock("./directory-live.js", () => ({
5
+ listMatrixDirectoryPeersLive: vi.fn(),
6
+ listMatrixDirectoryGroupsLive: vi.fn(),
7
+ }));
8
+
9
+ let listMatrixDirectoryGroupsLive: typeof import("./directory-live.js").listMatrixDirectoryGroupsLive;
10
+ let listMatrixDirectoryPeersLive: typeof import("./directory-live.js").listMatrixDirectoryPeersLive;
11
+ let resolveMatrixTargets: typeof import("./resolve-targets.js").resolveMatrixTargets;
12
+
13
+ async function resolveUserTarget(input = "Alice") {
14
+ const [result] = await resolveMatrixTargets({
15
+ cfg: {},
16
+ inputs: [input],
17
+ kind: "user",
18
+ });
19
+ return result;
20
+ }
21
+
22
+ describe("resolveMatrixTargets (users)", () => {
23
+ beforeAll(async () => {
24
+ ({ listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } =
25
+ await import("./directory-live.js"));
26
+ ({ resolveMatrixTargets } = await import("./resolve-targets.js"));
27
+ });
28
+
29
+ beforeEach(() => {
30
+ vi.mocked(listMatrixDirectoryPeersLive).mockReset();
31
+ vi.mocked(listMatrixDirectoryGroupsLive).mockReset();
32
+ });
33
+
34
+ it("resolves exact unique display name matches", async () => {
35
+ const matches: ChannelDirectoryEntry[] = [
36
+ { kind: "user", id: "@alice:example.org", name: "Alice" },
37
+ ];
38
+ vi.mocked(listMatrixDirectoryPeersLive).mockResolvedValue(matches);
39
+
40
+ const result = await resolveUserTarget();
41
+
42
+ expect(result?.resolved).toBe(true);
43
+ expect(result?.id).toBe("@alice:example.org");
44
+ expect(listMatrixDirectoryPeersLive).toHaveBeenCalledWith({
45
+ cfg: {},
46
+ accountId: undefined,
47
+ query: "Alice",
48
+ limit: 5,
49
+ });
50
+ });
51
+
52
+ it("does not resolve ambiguous or non-exact matches", async () => {
53
+ const matches: ChannelDirectoryEntry[] = [
54
+ { kind: "user", id: "@alice:example.org", name: "Alice" },
55
+ { kind: "user", id: "@alice:evil.example", name: "Alice" },
56
+ ];
57
+ vi.mocked(listMatrixDirectoryPeersLive).mockResolvedValue(matches);
58
+
59
+ const result = await resolveUserTarget();
60
+
61
+ expect(result?.resolved).toBe(false);
62
+ expect(result?.note).toMatch(/use full Matrix ID/i);
63
+ });
64
+
65
+ it("prefers exact group matches over first partial result", async () => {
66
+ const matches: ChannelDirectoryEntry[] = [
67
+ { kind: "group", id: "!one:example.org", name: "General", handle: "#general" },
68
+ { kind: "group", id: "!two:example.org", name: "Team", handle: "#team" },
69
+ ];
70
+ vi.mocked(listMatrixDirectoryGroupsLive).mockResolvedValue(matches);
71
+
72
+ const [result] = await resolveMatrixTargets({
73
+ cfg: {},
74
+ inputs: ["#team"],
75
+ kind: "group",
76
+ });
77
+
78
+ expect(result?.resolved).toBe(true);
79
+ expect(result?.id).toBe("!two:example.org");
80
+ expect(result?.note).toBeUndefined();
81
+ expect(listMatrixDirectoryGroupsLive).toHaveBeenCalledWith({
82
+ cfg: {},
83
+ accountId: undefined,
84
+ query: "#team",
85
+ limit: 5,
86
+ });
87
+ });
88
+
89
+ it("threads accountId into live Matrix target lookups", async () => {
90
+ vi.mocked(listMatrixDirectoryPeersLive).mockResolvedValue([
91
+ { kind: "user", id: "@alice:example.org", name: "Alice" },
92
+ ]);
93
+ vi.mocked(listMatrixDirectoryGroupsLive).mockResolvedValue([
94
+ { kind: "group", id: "!team:example.org", name: "Team", handle: "#team" },
95
+ ]);
96
+
97
+ await resolveMatrixTargets({
98
+ cfg: {},
99
+ accountId: "ops",
100
+ inputs: ["Alice"],
101
+ kind: "user",
102
+ });
103
+ await resolveMatrixTargets({
104
+ cfg: {},
105
+ accountId: "ops",
106
+ inputs: ["#team"],
107
+ kind: "group",
108
+ });
109
+
110
+ expect(listMatrixDirectoryPeersLive).toHaveBeenCalledWith({
111
+ cfg: {},
112
+ accountId: "ops",
113
+ query: "Alice",
114
+ limit: 5,
115
+ });
116
+ expect(listMatrixDirectoryGroupsLive).toHaveBeenCalledWith({
117
+ cfg: {},
118
+ accountId: "ops",
119
+ query: "#team",
120
+ limit: 5,
121
+ });
122
+ });
123
+
124
+ it("reuses directory lookups for normalized duplicate inputs", async () => {
125
+ vi.mocked(listMatrixDirectoryPeersLive).mockResolvedValue([
126
+ { kind: "user", id: "@alice:example.org", name: "Alice" },
127
+ ]);
128
+ vi.mocked(listMatrixDirectoryGroupsLive).mockResolvedValue([
129
+ { kind: "group", id: "!team:example.org", name: "Team", handle: "#team" },
130
+ ]);
131
+
132
+ const userResults = await resolveMatrixTargets({
133
+ cfg: {},
134
+ inputs: ["Alice", " alice "],
135
+ kind: "user",
136
+ });
137
+ const groupResults = await resolveMatrixTargets({
138
+ cfg: {},
139
+ inputs: ["#team", "#team"],
140
+ kind: "group",
141
+ });
142
+
143
+ expect(userResults.every((entry) => entry.resolved)).toBe(true);
144
+ expect(groupResults.every((entry) => entry.resolved)).toBe(true);
145
+ expect(listMatrixDirectoryPeersLive).toHaveBeenCalledTimes(1);
146
+ expect(listMatrixDirectoryGroupsLive).toHaveBeenCalledTimes(1);
147
+ });
148
+
149
+ it("accepts prefixed fully qualified ids without directory lookups", async () => {
150
+ const userResults = await resolveMatrixTargets({
151
+ cfg: {},
152
+ inputs: ["matrix:user:@alice:example.org"],
153
+ kind: "user",
154
+ });
155
+ const groupResults = await resolveMatrixTargets({
156
+ cfg: {},
157
+ inputs: ["matrix:room:!team:example.org"],
158
+ kind: "group",
159
+ });
160
+
161
+ expect(userResults).toEqual([
162
+ {
163
+ input: "matrix:user:@alice:example.org",
164
+ resolved: true,
165
+ id: "@alice:example.org",
166
+ },
167
+ ]);
168
+ expect(groupResults).toEqual([
169
+ {
170
+ input: "matrix:room:!team:example.org",
171
+ resolved: true,
172
+ id: "!team:example.org",
173
+ },
174
+ ]);
175
+ expect(listMatrixDirectoryPeersLive).not.toHaveBeenCalled();
176
+ expect(listMatrixDirectoryGroupsLive).not.toHaveBeenCalled();
177
+ });
178
+ });
@@ -0,0 +1,175 @@
1
+ import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
2
+ import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./directory-live.js";
3
+ import { isMatrixQualifiedUserId, normalizeMatrixMessagingTarget } from "./matrix/target-ids.js";
4
+ import type {
5
+ ChannelDirectoryEntry,
6
+ ChannelResolveKind,
7
+ ChannelResolveResult,
8
+ RuntimeEnv,
9
+ } from "./runtime-api.js";
10
+
11
+ function normalizeLookupQuery(query: string): string {
12
+ return normalizeOptionalLowercaseString(query) ?? "";
13
+ }
14
+
15
+ function findExactDirectoryMatches(
16
+ matches: ChannelDirectoryEntry[],
17
+ query: string,
18
+ ): ChannelDirectoryEntry[] {
19
+ const normalized = normalizeLookupQuery(query);
20
+ if (!normalized) {
21
+ return [];
22
+ }
23
+ return matches.filter((match) => {
24
+ const id = normalizeOptionalLowercaseString(match.id);
25
+ const name = normalizeOptionalLowercaseString(match.name);
26
+ const handle = normalizeOptionalLowercaseString(match.handle);
27
+ return normalized === id || normalized === name || normalized === handle;
28
+ });
29
+ }
30
+
31
+ function pickBestGroupMatch(
32
+ matches: ChannelDirectoryEntry[],
33
+ query: string,
34
+ ): { best?: ChannelDirectoryEntry; note?: string } {
35
+ if (matches.length === 0) {
36
+ return {};
37
+ }
38
+ const exact = findExactDirectoryMatches(matches, query);
39
+ if (exact.length > 1) {
40
+ return { best: exact[0], note: "multiple exact matches; chose first" };
41
+ }
42
+ if (exact.length === 1) {
43
+ return { best: exact[0] };
44
+ }
45
+ return {
46
+ best: matches[0],
47
+ note: matches.length > 1 ? "multiple matches; chose first" : undefined,
48
+ };
49
+ }
50
+
51
+ function pickBestUserMatch(
52
+ matches: ChannelDirectoryEntry[],
53
+ query: string,
54
+ ): ChannelDirectoryEntry | undefined {
55
+ if (matches.length === 0) {
56
+ return undefined;
57
+ }
58
+ const exact = findExactDirectoryMatches(matches, query);
59
+ if (exact.length === 1) {
60
+ return exact[0];
61
+ }
62
+ return undefined;
63
+ }
64
+
65
+ function describeUserMatchFailure(matches: ChannelDirectoryEntry[], query: string): string {
66
+ if (matches.length === 0) {
67
+ return "no matches";
68
+ }
69
+ const normalized = normalizeLookupQuery(query);
70
+ if (!normalized) {
71
+ return "empty input";
72
+ }
73
+ const exact = findExactDirectoryMatches(matches, normalized);
74
+ if (exact.length === 0) {
75
+ return "no exact match; use full Matrix ID";
76
+ }
77
+ if (exact.length > 1) {
78
+ return "multiple exact matches; use full Matrix ID";
79
+ }
80
+ return "no exact match; use full Matrix ID";
81
+ }
82
+
83
+ async function readCachedMatches(
84
+ cache: Map<string, ChannelDirectoryEntry[]>,
85
+ query: string,
86
+ lookup: (query: string) => Promise<ChannelDirectoryEntry[]>,
87
+ ): Promise<ChannelDirectoryEntry[]> {
88
+ const key = normalizeLookupQuery(query);
89
+ if (!key) {
90
+ return [];
91
+ }
92
+ const cached = cache.get(key);
93
+ if (cached) {
94
+ return cached;
95
+ }
96
+ const matches = await lookup(query.trim());
97
+ cache.set(key, matches);
98
+ return matches;
99
+ }
100
+
101
+ export async function resolveMatrixTargets(params: {
102
+ cfg: unknown;
103
+ accountId?: string | null;
104
+ inputs: string[];
105
+ kind: ChannelResolveKind;
106
+ runtime?: RuntimeEnv;
107
+ }): Promise<ChannelResolveResult[]> {
108
+ const results: ChannelResolveResult[] = [];
109
+ const userLookupCache = new Map<string, ChannelDirectoryEntry[]>();
110
+ const groupLookupCache = new Map<string, ChannelDirectoryEntry[]>();
111
+
112
+ for (const input of params.inputs) {
113
+ const trimmed = input.trim();
114
+ if (!trimmed) {
115
+ results.push({ input, resolved: false, note: "empty input" });
116
+ continue;
117
+ }
118
+ if (params.kind === "user") {
119
+ const normalizedTarget = normalizeMatrixMessagingTarget(trimmed);
120
+ if (normalizedTarget && isMatrixQualifiedUserId(normalizedTarget)) {
121
+ results.push({ input, resolved: true, id: normalizedTarget });
122
+ continue;
123
+ }
124
+ try {
125
+ const matches = await readCachedMatches(userLookupCache, trimmed, (query) =>
126
+ listMatrixDirectoryPeersLive({
127
+ cfg: params.cfg,
128
+ accountId: params.accountId,
129
+ query,
130
+ limit: 5,
131
+ }),
132
+ );
133
+ const best = pickBestUserMatch(matches, trimmed);
134
+ results.push({
135
+ input,
136
+ resolved: Boolean(best?.id),
137
+ id: best?.id,
138
+ name: best?.name,
139
+ note: best ? undefined : describeUserMatchFailure(matches, trimmed),
140
+ });
141
+ } catch (err) {
142
+ params.runtime?.error?.(`matrix resolve failed: ${String(err)}`);
143
+ results.push({ input, resolved: false, note: "lookup failed" });
144
+ }
145
+ continue;
146
+ }
147
+ const normalizedTarget = normalizeMatrixMessagingTarget(trimmed);
148
+ if (normalizedTarget?.startsWith("!")) {
149
+ results.push({ input, resolved: true, id: normalizedTarget });
150
+ continue;
151
+ }
152
+ try {
153
+ const matches = await readCachedMatches(groupLookupCache, trimmed, (query) =>
154
+ listMatrixDirectoryGroupsLive({
155
+ cfg: params.cfg,
156
+ accountId: params.accountId,
157
+ query,
158
+ limit: 5,
159
+ }),
160
+ );
161
+ const { best, note } = pickBestGroupMatch(matches, trimmed);
162
+ results.push({
163
+ input,
164
+ resolved: Boolean(best?.id),
165
+ id: best?.id,
166
+ name: best?.name,
167
+ note,
168
+ });
169
+ } catch (err) {
170
+ params.runtime?.error?.(`matrix resolve failed: ${String(err)}`);
171
+ results.push({ input, resolved: false, note: "lookup failed" });
172
+ }
173
+ }
174
+ return results;
175
+ }
@@ -0,0 +1,21 @@
1
+ import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core";
2
+ import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
3
+ import type { ResolvedMatrixAccount } from "./matrix/accounts.js";
4
+
5
+ const loadMatrixChannelRuntime = createLazyRuntimeNamedExport(
6
+ () => import("./channel.runtime.js"),
7
+ "matrixChannelRuntime",
8
+ );
9
+
10
+ type MatrixResolver = NonNullable<ChannelPlugin<ResolvedMatrixAccount>["resolver"]>;
11
+
12
+ export const matrixResolverAdapter: MatrixResolver = {
13
+ resolveTargets: async ({ cfg, accountId, inputs, kind, runtime }) =>
14
+ (await loadMatrixChannelRuntime()).resolveMatrixTargets({
15
+ cfg,
16
+ accountId,
17
+ inputs,
18
+ kind,
19
+ runtime,
20
+ }),
21
+ };
@@ -0,0 +1,144 @@
1
+ export {
2
+ DEFAULT_ACCOUNT_ID,
3
+ normalizeAccountId,
4
+ normalizeOptionalAccountId,
5
+ } from "openclaw/plugin-sdk/account-id";
6
+ export {
7
+ createActionGate,
8
+ jsonResult,
9
+ readNumberParam,
10
+ readReactionParams,
11
+ readStringArrayParam,
12
+ readStringParam,
13
+ ToolAuthorizationError,
14
+ } from "openclaw/plugin-sdk/channel-actions";
15
+ export { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-primitives";
16
+ export type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core";
17
+ export type {
18
+ BaseProbeResult,
19
+ ChannelDirectoryEntry,
20
+ ChannelGroupContext,
21
+ ChannelMessageActionAdapter,
22
+ ChannelMessageActionContext,
23
+ ChannelMessageActionName,
24
+ ChannelMessageToolDiscovery,
25
+ ChannelOutboundAdapter,
26
+ ChannelResolveKind,
27
+ ChannelResolveResult,
28
+ ChannelToolSend,
29
+ } from "openclaw/plugin-sdk/channel-contract";
30
+ export {
31
+ formatLocationText,
32
+ logInboundDrop,
33
+ toLocationContext,
34
+ type NormalizedLocation,
35
+ } from "openclaw/plugin-sdk/channel-inbound";
36
+ export { resolveAckReaction, logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
37
+ export type { ChannelSetupInput } from "openclaw/plugin-sdk/setup";
38
+ export type {
39
+ OpenClawConfig,
40
+ ContextVisibilityMode,
41
+ DmPolicy,
42
+ GroupPolicy,
43
+ } from "openclaw/plugin-sdk/config-runtime";
44
+ export type { GroupToolPolicyConfig } from "openclaw/plugin-sdk/config-runtime";
45
+ export type { WizardPrompter } from "openclaw/plugin-sdk/matrix-runtime-shared";
46
+ export type { SecretInput } from "openclaw/plugin-sdk/secret-input";
47
+ export {
48
+ GROUP_POLICY_BLOCKED_LABEL,
49
+ resolveAllowlistProviderRuntimeGroupPolicy,
50
+ resolveDefaultGroupPolicy,
51
+ warnMissingProviderGroupPolicyFallbackOnce,
52
+ } from "openclaw/plugin-sdk/config-runtime";
53
+ export {
54
+ addWildcardAllowFrom,
55
+ formatDocsLink,
56
+ hasConfiguredSecretInput,
57
+ mergeAllowFromEntries,
58
+ moveSingleAccountChannelSectionToDefaultAccount,
59
+ promptAccountId,
60
+ promptChannelAccessConfig,
61
+ splitSetupEntries,
62
+ } from "openclaw/plugin-sdk/setup";
63
+ export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
64
+ export {
65
+ assertHttpUrlTargetsPrivateNetwork,
66
+ closeDispatcher,
67
+ createPinnedDispatcher,
68
+ isPrivateOrLoopbackHost,
69
+ resolvePinnedHostnameWithPolicy,
70
+ ssrfPolicyFromDangerouslyAllowPrivateNetwork,
71
+ ssrfPolicyFromAllowPrivateNetwork,
72
+ type LookupFn,
73
+ type SsrFPolicy,
74
+ } from "openclaw/plugin-sdk/ssrf-runtime";
75
+ export { dispatchReplyFromConfigWithSettledDispatcher } from "openclaw/plugin-sdk/inbound-reply-dispatch";
76
+ export {
77
+ ensureConfiguredAcpBindingReady,
78
+ resolveConfiguredAcpBindingRecord,
79
+ } from "openclaw/plugin-sdk/acp-binding-runtime";
80
+ export {
81
+ buildProbeChannelStatusSummary,
82
+ collectStatusIssuesFromLastError,
83
+ PAIRING_APPROVED_MESSAGE,
84
+ } from "openclaw/plugin-sdk/channel-status";
85
+ export {
86
+ getSessionBindingService,
87
+ resolveThreadBindingIdleTimeoutMsForChannel,
88
+ resolveThreadBindingMaxAgeMsForChannel,
89
+ } from "openclaw/plugin-sdk/conversation-runtime";
90
+ export { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-runtime";
91
+ export { resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/routing";
92
+ export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
93
+ export { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
94
+ export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
95
+ export { normalizePollInput, type PollInput } from "openclaw/plugin-sdk/media-runtime";
96
+ export { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
97
+ export {
98
+ buildChannelKeyCandidates,
99
+ resolveChannelEntryMatch,
100
+ } from "openclaw/plugin-sdk/channel-targets";
101
+ export {
102
+ evaluateGroupRouteAccessForPolicy,
103
+ resolveSenderScopedGroupPolicy,
104
+ } from "openclaw/plugin-sdk/channel-policy";
105
+ export {
106
+ formatZonedTimestamp,
107
+ type PluginRuntime,
108
+ type RuntimeLogger,
109
+ } from "openclaw/plugin-sdk/matrix-runtime-shared";
110
+ export type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
111
+ // resolveMatrixAccountStringValues already comes from plugin-sdk/matrix.
112
+ // Re-exporting auth-precedence here makes Jiti try to define the same export twice.
113
+
114
+ export function buildTimeoutAbortSignal(params: { timeoutMs?: number; signal?: AbortSignal }): {
115
+ signal?: AbortSignal;
116
+ cleanup: () => void;
117
+ } {
118
+ const { timeoutMs, signal } = params;
119
+ if (!timeoutMs && !signal) {
120
+ return { signal: undefined, cleanup: () => {} };
121
+ }
122
+ if (!timeoutMs) {
123
+ return { signal, cleanup: () => {} };
124
+ }
125
+
126
+ const controller = new AbortController();
127
+ const timeoutId = setTimeout(controller.abort.bind(controller), timeoutMs);
128
+ const onAbort = () => controller.abort();
129
+ if (signal) {
130
+ if (signal.aborted) {
131
+ controller.abort();
132
+ } else {
133
+ signal.addEventListener("abort", onAbort, { once: true });
134
+ }
135
+ }
136
+
137
+ return {
138
+ signal: controller.signal,
139
+ cleanup: () => {
140
+ clearTimeout(timeoutId);
141
+ signal?.removeEventListener("abort", onAbort);
142
+ },
143
+ };
144
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
2
+ import type { PluginRuntime } from "./runtime-api.js";
3
+
4
+ const { setRuntime: setMatrixRuntime, getRuntime: getMatrixRuntime } =
5
+ createPluginRuntimeStore<PluginRuntime>("Matrix runtime not initialized");
6
+
7
+ export { getMatrixRuntime, setMatrixRuntime };
@@ -0,0 +1,174 @@
1
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
+ import {
3
+ collectSecretInputAssignment,
4
+ getChannelSurface,
5
+ hasConfiguredSecretInputValue,
6
+ hasOwnProperty,
7
+ normalizeSecretStringValue,
8
+ type ResolverContext,
9
+ type SecretDefaults,
10
+ type SecretTargetRegistryEntry,
11
+ } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
12
+ import { getLobiScopedEnvVarNames as getMatrixScopedEnvVarNames } from "./env-vars.js";
13
+
14
+ export const secretTargetRegistryEntries = [
15
+ {
16
+ id: "channels.lobi.accounts.*.accessToken",
17
+ targetType: "channels.lobi.accounts.*.accessToken",
18
+ configFile: "openclaw.json",
19
+ pathPattern: "channels.lobi.accounts.*.accessToken",
20
+ secretShape: "secret_input",
21
+ expectedResolvedValue: "string",
22
+ includeInPlan: true,
23
+ includeInConfigure: true,
24
+ includeInAudit: true,
25
+ },
26
+ {
27
+ id: "channels.lobi.accounts.*.password",
28
+ targetType: "channels.lobi.accounts.*.password",
29
+ configFile: "openclaw.json",
30
+ pathPattern: "channels.lobi.accounts.*.password",
31
+ secretShape: "secret_input",
32
+ expectedResolvedValue: "string",
33
+ includeInPlan: true,
34
+ includeInConfigure: true,
35
+ includeInAudit: true,
36
+ },
37
+ {
38
+ id: "channels.lobi.accessToken",
39
+ targetType: "channels.lobi.accessToken",
40
+ configFile: "openclaw.json",
41
+ pathPattern: "channels.lobi.accessToken",
42
+ secretShape: "secret_input",
43
+ expectedResolvedValue: "string",
44
+ includeInPlan: true,
45
+ includeInConfigure: true,
46
+ includeInAudit: true,
47
+ },
48
+ {
49
+ id: "channels.lobi.password",
50
+ targetType: "channels.lobi.password",
51
+ configFile: "openclaw.json",
52
+ pathPattern: "channels.lobi.password",
53
+ secretShape: "secret_input",
54
+ expectedResolvedValue: "string",
55
+ includeInPlan: true,
56
+ includeInConfigure: true,
57
+ includeInAudit: true,
58
+ },
59
+ ] satisfies SecretTargetRegistryEntry[];
60
+
61
+ export function collectRuntimeConfigAssignments(params: {
62
+ config: { channels?: Record<string, unknown> };
63
+ defaults?: SecretDefaults;
64
+ context: ResolverContext;
65
+ }): void {
66
+ const resolved = getChannelSurface(params.config, "matrix");
67
+ if (!resolved) {
68
+ return;
69
+ }
70
+ const { channel: matrix, surface } = resolved;
71
+ const envAccessTokenConfigured =
72
+ normalizeSecretStringValue(params.context.env.LOBI_ACCESS_TOKEN).length > 0;
73
+ const defaultScopedAccessTokenConfigured =
74
+ normalizeSecretStringValue(
75
+ params.context.env[getMatrixScopedEnvVarNames("default").accessToken],
76
+ ).length > 0;
77
+ const defaultAccountAccessTokenConfigured = surface.accounts.some(
78
+ ({ accountId, account }) =>
79
+ normalizeAccountId(accountId) === DEFAULT_ACCOUNT_ID &&
80
+ hasConfiguredSecretInputValue(account.accessToken, params.defaults),
81
+ );
82
+ const baseAccessTokenConfigured = hasConfiguredSecretInputValue(
83
+ matrix.accessToken,
84
+ params.defaults,
85
+ );
86
+ collectSecretInputAssignment({
87
+ value: matrix.accessToken,
88
+ path: "channels.lobi.accessToken",
89
+ expected: "string",
90
+ defaults: params.defaults,
91
+ context: params.context,
92
+ active: surface.channelEnabled,
93
+ inactiveReason: "Matrix channel is disabled.",
94
+ apply: (value) => {
95
+ matrix.accessToken = value;
96
+ },
97
+ });
98
+ collectSecretInputAssignment({
99
+ value: matrix.password,
100
+ path: "channels.lobi.password",
101
+ expected: "string",
102
+ defaults: params.defaults,
103
+ context: params.context,
104
+ active:
105
+ surface.channelEnabled &&
106
+ !(
107
+ baseAccessTokenConfigured ||
108
+ envAccessTokenConfigured ||
109
+ defaultScopedAccessTokenConfigured ||
110
+ defaultAccountAccessTokenConfigured
111
+ ),
112
+ inactiveReason:
113
+ "Matrix channel is disabled or access-token auth is configured for the default Matrix account.",
114
+ apply: (value) => {
115
+ matrix.password = value;
116
+ },
117
+ });
118
+ if (!surface.hasExplicitAccounts) {
119
+ return;
120
+ }
121
+ for (const { accountId, account, enabled } of surface.accounts) {
122
+ if (hasOwnProperty(account, "accessToken")) {
123
+ collectSecretInputAssignment({
124
+ value: account.accessToken,
125
+ path: `channels.lobi.accounts.${accountId}.accessToken`,
126
+ expected: "string",
127
+ defaults: params.defaults,
128
+ context: params.context,
129
+ active: enabled,
130
+ inactiveReason: "Matrix account is disabled.",
131
+ apply: (value) => {
132
+ account.accessToken = value;
133
+ },
134
+ });
135
+ }
136
+ if (!hasOwnProperty(account, "password")) {
137
+ continue;
138
+ }
139
+ const accountAccessTokenConfigured = hasConfiguredSecretInputValue(
140
+ account.accessToken,
141
+ params.defaults,
142
+ );
143
+ const scopedEnvAccessTokenConfigured =
144
+ normalizeSecretStringValue(
145
+ params.context.env[getMatrixScopedEnvVarNames(accountId).accessToken],
146
+ ).length > 0;
147
+ const inheritedDefaultAccountAccessTokenConfigured =
148
+ normalizeAccountId(accountId) === DEFAULT_ACCOUNT_ID &&
149
+ (baseAccessTokenConfigured || envAccessTokenConfigured);
150
+ collectSecretInputAssignment({
151
+ value: account.password,
152
+ path: `channels.lobi.accounts.${accountId}.password`,
153
+ expected: "string",
154
+ defaults: params.defaults,
155
+ context: params.context,
156
+ active:
157
+ enabled &&
158
+ !(
159
+ accountAccessTokenConfigured ||
160
+ scopedEnvAccessTokenConfigured ||
161
+ inheritedDefaultAccountAccessTokenConfigured
162
+ ),
163
+ inactiveReason: "Matrix account is disabled or this account has an accessToken configured.",
164
+ apply: (value) => {
165
+ account.password = value;
166
+ },
167
+ });
168
+ }
169
+ }
170
+
171
+ export const channelSecrets = {
172
+ secretTargetRegistryEntries,
173
+ collectRuntimeConfigAssignments,
174
+ };