@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,251 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import type { PluginRuntime } from "../runtime-api.js";
3
+ import { matrixMessageActions } from "./actions.js";
4
+ import { setMatrixRuntime } from "./runtime.js";
5
+ import type { CoreConfig } from "./types.js";
6
+
7
+ const profileAction = "set-profile" as const;
8
+
9
+ const runtimeStub = {
10
+ config: {
11
+ loadConfig: () => ({}),
12
+ },
13
+ media: {
14
+ loadWebMedia: async () => {
15
+ throw new Error("not used");
16
+ },
17
+ mediaKindFromMime: () => "image",
18
+ isVoiceCompatibleAudio: () => false,
19
+ getImageMetadata: async () => null,
20
+ resizeToJpeg: async () => Buffer.from(""),
21
+ },
22
+ state: {
23
+ resolveStateDir: () => "/tmp/openclaw-matrix-test",
24
+ },
25
+ channel: {
26
+ text: {
27
+ resolveTextChunkLimit: () => 4000,
28
+ resolveChunkMode: () => "length",
29
+ chunkMarkdownText: (text: string) => (text ? [text] : []),
30
+ chunkMarkdownTextWithMode: (text: string) => (text ? [text] : []),
31
+ resolveMarkdownTableMode: () => "code",
32
+ convertMarkdownTables: (text: string) => text,
33
+ },
34
+ },
35
+ } as unknown as PluginRuntime;
36
+
37
+ function createConfiguredMatrixConfig(): CoreConfig {
38
+ return {
39
+ channels: {
40
+ matrix: {
41
+ enabled: true,
42
+ homeserver: "https://matrix.example.org",
43
+ userId: "@bot:example.org",
44
+ accessToken: "token",
45
+ },
46
+ },
47
+ } as CoreConfig;
48
+ }
49
+
50
+ describe("matrixMessageActions", () => {
51
+ beforeEach(() => {
52
+ setMatrixRuntime(runtimeStub);
53
+ });
54
+
55
+ it("exposes poll create but only handles poll votes inside the plugin", () => {
56
+ const describeMessageTool = matrixMessageActions.describeMessageTool;
57
+ const supportsAction = matrixMessageActions.supportsAction ?? (() => false);
58
+
59
+ expect(describeMessageTool).toBeTypeOf("function");
60
+ expect(supportsAction).toBeTypeOf("function");
61
+
62
+ const discovery = describeMessageTool({
63
+ cfg: createConfiguredMatrixConfig(),
64
+ } as never);
65
+ if (!discovery) {
66
+ throw new Error("describeMessageTool returned null");
67
+ }
68
+ const actions = discovery.actions;
69
+ expect(actions).toContain("poll");
70
+ expect(actions).toContain("poll-vote");
71
+ expect(supportsAction({ action: "poll" } as never)).toBe(false);
72
+ expect(supportsAction({ action: "poll-vote" } as never)).toBe(true);
73
+ });
74
+
75
+ it("exposes and describes self-profile updates", () => {
76
+ const describeMessageTool = matrixMessageActions.describeMessageTool;
77
+ const supportsAction = matrixMessageActions.supportsAction ?? (() => false);
78
+
79
+ const discovery = describeMessageTool({
80
+ cfg: createConfiguredMatrixConfig(),
81
+ senderIsOwner: true,
82
+ } as never);
83
+ if (!discovery) {
84
+ throw new Error("describeMessageTool returned null");
85
+ }
86
+ const actions = discovery.actions;
87
+ const schema = discovery.schema;
88
+ if (!schema) {
89
+ throw new Error("matrix schema missing");
90
+ }
91
+ const properties = (schema as { properties?: Record<string, unknown> }).properties ?? {};
92
+
93
+ expect(actions).toContain(profileAction);
94
+ expect(supportsAction({ action: profileAction } as never)).toBe(true);
95
+ expect(properties.displayName).toBeDefined();
96
+ expect(properties.avatarUrl).toBeDefined();
97
+ expect(properties.avatarPath).toBeDefined();
98
+ });
99
+
100
+ it("hides self-profile updates for non-owner discovery", () => {
101
+ const discovery = matrixMessageActions.describeMessageTool({
102
+ cfg: createConfiguredMatrixConfig(),
103
+ senderIsOwner: false,
104
+ } as never);
105
+ if (!discovery) {
106
+ throw new Error("describeMessageTool returned null");
107
+ }
108
+
109
+ expect(discovery.actions).not.toContain(profileAction);
110
+ expect(discovery.schema).toBeNull();
111
+ });
112
+
113
+ it("hides self-profile updates when owner status is unknown", () => {
114
+ const discovery = matrixMessageActions.describeMessageTool({
115
+ cfg: createConfiguredMatrixConfig(),
116
+ } as never);
117
+ if (!discovery) {
118
+ throw new Error("describeMessageTool returned null");
119
+ }
120
+
121
+ expect(discovery.actions).not.toContain(profileAction);
122
+ expect(discovery.schema).toBeNull();
123
+ });
124
+
125
+ it("hides gated actions when the default Matrix account disables them", () => {
126
+ const discovery = matrixMessageActions.describeMessageTool({
127
+ cfg: {
128
+ channels: {
129
+ matrix: {
130
+ defaultAccount: "assistant",
131
+ actions: {
132
+ messages: true,
133
+ reactions: true,
134
+ pins: true,
135
+ profile: true,
136
+ memberInfo: true,
137
+ channelInfo: true,
138
+ verification: true,
139
+ },
140
+ accounts: {
141
+ assistant: {
142
+ homeserver: "https://matrix.example.org",
143
+ userId: "@bot:example.org",
144
+ accessToken: "token",
145
+ encryption: true,
146
+ actions: {
147
+ messages: false,
148
+ reactions: false,
149
+ pins: false,
150
+ profile: false,
151
+ memberInfo: false,
152
+ channelInfo: false,
153
+ verification: false,
154
+ },
155
+ },
156
+ },
157
+ },
158
+ },
159
+ } as CoreConfig,
160
+ } as never);
161
+ if (!discovery) {
162
+ throw new Error("describeMessageTool returned null");
163
+ }
164
+ const actions = discovery.actions;
165
+
166
+ expect(actions).toEqual(["poll", "poll-vote"]);
167
+ });
168
+
169
+ it("hides actions until defaultAccount is set for ambiguous multi-account configs", () => {
170
+ const discovery = matrixMessageActions.describeMessageTool({
171
+ cfg: {
172
+ channels: {
173
+ matrix: {
174
+ accounts: {
175
+ assistant: {
176
+ homeserver: "https://matrix.example.org",
177
+ accessToken: "assistant-token",
178
+ },
179
+ ops: {
180
+ homeserver: "https://matrix.example.org",
181
+ accessToken: "ops-token",
182
+ },
183
+ },
184
+ },
185
+ },
186
+ } as CoreConfig,
187
+ } as never);
188
+ if (!discovery) {
189
+ throw new Error("describeMessageTool returned null");
190
+ }
191
+ const actions = discovery.actions;
192
+
193
+ expect(actions).toEqual([]);
194
+ });
195
+
196
+ it("honors the selected Matrix account during discovery", () => {
197
+ const cfg = {
198
+ channels: {
199
+ matrix: {
200
+ defaultAccount: "assistant",
201
+ accounts: {
202
+ assistant: {
203
+ homeserver: "https://matrix.example.org",
204
+ userId: "@assistant:example.org",
205
+ accessToken: "assistant-token",
206
+ actions: {
207
+ messages: true,
208
+ reactions: false,
209
+ },
210
+ },
211
+ ops: {
212
+ homeserver: "https://matrix.example.org",
213
+ userId: "@ops:example.org",
214
+ accessToken: "ops-token",
215
+ actions: {
216
+ messages: true,
217
+ reactions: true,
218
+ },
219
+ },
220
+ },
221
+ },
222
+ },
223
+ } as CoreConfig;
224
+
225
+ const describeMessageTool = matrixMessageActions.describeMessageTool;
226
+ if (!describeMessageTool) {
227
+ throw new Error("matrix message action discovery is unavailable");
228
+ }
229
+
230
+ const assistantDiscovery = describeMessageTool({
231
+ cfg,
232
+ accountId: "assistant",
233
+ } as never);
234
+ const opsDiscovery = describeMessageTool({
235
+ cfg,
236
+ accountId: "ops",
237
+ } as never);
238
+
239
+ if (!assistantDiscovery || !opsDiscovery) {
240
+ throw new Error("matrix action discovery returned null");
241
+ }
242
+
243
+ const assistantActions = assistantDiscovery.actions;
244
+ const opsActions = opsDiscovery.actions;
245
+
246
+ expect(assistantActions).not.toContain("react");
247
+ expect(assistantActions).not.toContain("reactions");
248
+ expect(opsActions).toContain("react");
249
+ expect(opsActions).toContain("reactions");
250
+ });
251
+ });
package/src/actions.ts ADDED
@@ -0,0 +1,336 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
3
+ import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
4
+ import { requiresExplicitMatrixDefaultAccount } from "./account-selection.js";
5
+ import { resolveDefaultMatrixAccountId, resolveMatrixAccount } from "./matrix/accounts.js";
6
+ import {
7
+ createActionGate,
8
+ readNumberParam,
9
+ readStringParam,
10
+ ToolAuthorizationError,
11
+ type ChannelMessageActionAdapter,
12
+ type ChannelMessageActionContext,
13
+ type ChannelMessageActionName,
14
+ type ChannelMessageToolDiscovery,
15
+ } from "./runtime-api.js";
16
+ import type { CoreConfig } from "./types.js";
17
+
18
+ const MATRIX_PLUGIN_HANDLED_ACTIONS = new Set<ChannelMessageActionName>([
19
+ "send",
20
+ "poll-vote",
21
+ "react",
22
+ "reactions",
23
+ "read",
24
+ "edit",
25
+ "delete",
26
+ "pin",
27
+ "unpin",
28
+ "list-pins",
29
+ "set-profile",
30
+ "member-info",
31
+ "channel-info",
32
+ "permissions",
33
+ ]);
34
+
35
+ function createMatrixExposedActions(params: {
36
+ gate: ReturnType<typeof createActionGate>;
37
+ encryptionEnabled: boolean;
38
+ senderIsOwner?: boolean;
39
+ }) {
40
+ const actions = new Set<ChannelMessageActionName>(["poll", "poll-vote"]);
41
+ if (params.gate("messages")) {
42
+ actions.add("send");
43
+ actions.add("read");
44
+ actions.add("edit");
45
+ actions.add("delete");
46
+ }
47
+ if (params.gate("reactions")) {
48
+ actions.add("react");
49
+ actions.add("reactions");
50
+ }
51
+ if (params.gate("pins")) {
52
+ actions.add("pin");
53
+ actions.add("unpin");
54
+ actions.add("list-pins");
55
+ }
56
+ if (params.gate("profile") && params.senderIsOwner === true) {
57
+ actions.add("set-profile");
58
+ }
59
+ if (params.gate("memberInfo")) {
60
+ actions.add("member-info");
61
+ }
62
+ if (params.gate("channelInfo")) {
63
+ actions.add("channel-info");
64
+ }
65
+ if (params.encryptionEnabled && params.gate("verification")) {
66
+ actions.add("permissions");
67
+ }
68
+ return actions;
69
+ }
70
+
71
+ function buildMatrixProfileToolSchema(): NonNullable<ChannelMessageToolDiscovery["schema"]> {
72
+ return {
73
+ properties: {
74
+ displayName: Type.Optional(
75
+ Type.String({
76
+ description: "Profile display name for Matrix self-profile update actions.",
77
+ }),
78
+ ),
79
+ display_name: Type.Optional(
80
+ Type.String({
81
+ description: "snake_case alias of displayName for Matrix self-profile update actions.",
82
+ }),
83
+ ),
84
+ avatarUrl: Type.Optional(
85
+ Type.String({
86
+ description:
87
+ "Profile avatar URL for Matrix self-profile update actions. Matrix accepts mxc:// and http(s) URLs.",
88
+ }),
89
+ ),
90
+ avatar_url: Type.Optional(
91
+ Type.String({
92
+ description:
93
+ "snake_case alias of avatarUrl for Matrix self-profile update actions. Matrix accepts mxc:// and http(s) URLs.",
94
+ }),
95
+ ),
96
+ avatarPath: Type.Optional(
97
+ Type.String({
98
+ description:
99
+ "Local avatar file path for Matrix self-profile update actions. Matrix uploads this file and sets the resulting MXC URI.",
100
+ }),
101
+ ),
102
+ avatar_path: Type.Optional(
103
+ Type.String({
104
+ description:
105
+ "snake_case alias of avatarPath for Matrix self-profile update actions. Matrix uploads this file and sets the resulting MXC URI.",
106
+ }),
107
+ ),
108
+ },
109
+ };
110
+ }
111
+
112
+ export const matrixMessageActions: ChannelMessageActionAdapter = {
113
+ describeMessageTool: ({ cfg, accountId, senderIsOwner }) => {
114
+ const resolvedCfg = cfg as CoreConfig;
115
+ if (!accountId && requiresExplicitMatrixDefaultAccount(resolvedCfg)) {
116
+ return { actions: [], capabilities: [] };
117
+ }
118
+ const account = resolveMatrixAccount({
119
+ cfg: resolvedCfg,
120
+ accountId: accountId ?? resolveDefaultMatrixAccountId(resolvedCfg),
121
+ });
122
+ if (!account.enabled || !account.configured) {
123
+ return { actions: [], capabilities: [] };
124
+ }
125
+ const gate = createActionGate(account.config.actions);
126
+ const actions = createMatrixExposedActions({
127
+ gate,
128
+ encryptionEnabled: account.config.encryption === true,
129
+ senderIsOwner,
130
+ });
131
+ const listedActions = Array.from(actions);
132
+ return {
133
+ actions: listedActions,
134
+ capabilities: [],
135
+ schema: listedActions.includes("set-profile") ? buildMatrixProfileToolSchema() : null,
136
+ };
137
+ },
138
+ supportsAction: ({ action }) => MATRIX_PLUGIN_HANDLED_ACTIONS.has(action),
139
+ extractToolSend: ({ args }) => {
140
+ return extractToolSend(args, "sendMessage");
141
+ },
142
+ handleAction: async (ctx: ChannelMessageActionContext) => {
143
+ const { handleMatrixAction } = await import("./tool-actions.runtime.js");
144
+ const { action, params, cfg, accountId, mediaLocalRoots } = ctx;
145
+ const dispatch = async (actionParams: Record<string, unknown>) =>
146
+ await handleMatrixAction(
147
+ {
148
+ ...actionParams,
149
+ ...(accountId ? { accountId } : {}),
150
+ },
151
+ cfg as CoreConfig,
152
+ { mediaLocalRoots },
153
+ );
154
+ const resolveRoomId = () =>
155
+ readStringParam(params, "roomId") ??
156
+ readStringParam(params, "channelId") ??
157
+ readStringParam(params, "to", { required: true });
158
+
159
+ if (action === "send") {
160
+ const to = readStringParam(params, "to", { required: true });
161
+ const mediaUrl =
162
+ readStringParam(params, "media", { trim: false }) ??
163
+ readStringParam(params, "mediaUrl", { trim: false }) ??
164
+ readStringParam(params, "filePath", { trim: false }) ??
165
+ readStringParam(params, "path", { trim: false });
166
+ const content = readStringParam(params, "message", {
167
+ required: !mediaUrl,
168
+ allowEmpty: true,
169
+ });
170
+ const replyTo = readStringParam(params, "replyTo");
171
+ const threadId = readStringParam(params, "threadId");
172
+ const audioAsVoice =
173
+ typeof params.asVoice === "boolean"
174
+ ? params.asVoice
175
+ : typeof params.audioAsVoice === "boolean"
176
+ ? params.audioAsVoice
177
+ : undefined;
178
+ return await dispatch({
179
+ action: "sendMessage",
180
+ to,
181
+ content,
182
+ mediaUrl: mediaUrl ?? undefined,
183
+ replyToId: replyTo ?? undefined,
184
+ threadId: threadId ?? undefined,
185
+ audioAsVoice,
186
+ });
187
+ }
188
+
189
+ if (action === "poll-vote") {
190
+ return await dispatch({
191
+ ...params,
192
+ action: "pollVote",
193
+ });
194
+ }
195
+
196
+ if (action === "react") {
197
+ const messageId = readStringParam(params, "messageId", { required: true });
198
+ const emoji = readStringParam(params, "emoji", { allowEmpty: true });
199
+ const remove = typeof params.remove === "boolean" ? params.remove : undefined;
200
+ return await dispatch({
201
+ action: "react",
202
+ roomId: resolveRoomId(),
203
+ messageId,
204
+ emoji,
205
+ remove,
206
+ });
207
+ }
208
+
209
+ if (action === "reactions") {
210
+ const messageId = readStringParam(params, "messageId", { required: true });
211
+ const limit = readNumberParam(params, "limit", { integer: true });
212
+ return await dispatch({
213
+ action: "reactions",
214
+ roomId: resolveRoomId(),
215
+ messageId,
216
+ limit,
217
+ });
218
+ }
219
+
220
+ if (action === "read") {
221
+ const limit = readNumberParam(params, "limit", { integer: true });
222
+ return await dispatch({
223
+ action: "readMessages",
224
+ roomId: resolveRoomId(),
225
+ limit,
226
+ before: readStringParam(params, "before"),
227
+ after: readStringParam(params, "after"),
228
+ });
229
+ }
230
+
231
+ if (action === "edit") {
232
+ const messageId = readStringParam(params, "messageId", { required: true });
233
+ const content = readStringParam(params, "message", { required: true });
234
+ return await dispatch({
235
+ action: "editMessage",
236
+ roomId: resolveRoomId(),
237
+ messageId,
238
+ content,
239
+ });
240
+ }
241
+
242
+ if (action === "delete") {
243
+ const messageId = readStringParam(params, "messageId", { required: true });
244
+ return await dispatch({
245
+ action: "deleteMessage",
246
+ roomId: resolveRoomId(),
247
+ messageId,
248
+ });
249
+ }
250
+
251
+ if (action === "pin" || action === "unpin" || action === "list-pins") {
252
+ const messageId =
253
+ action === "list-pins"
254
+ ? undefined
255
+ : readStringParam(params, "messageId", { required: true });
256
+ return await dispatch({
257
+ action: action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins",
258
+ roomId: resolveRoomId(),
259
+ messageId,
260
+ });
261
+ }
262
+
263
+ if (action === "set-profile") {
264
+ if (ctx.senderIsOwner !== true) {
265
+ throw new ToolAuthorizationError("Matrix profile updates require owner access.");
266
+ }
267
+ const avatarPath =
268
+ readStringParam(params, "avatarPath") ??
269
+ readStringParam(params, "path") ??
270
+ readStringParam(params, "filePath");
271
+ return await dispatch({
272
+ action: "setProfile",
273
+ displayName: readStringParam(params, "displayName") ?? readStringParam(params, "name"),
274
+ avatarUrl: readStringParam(params, "avatarUrl"),
275
+ avatarPath,
276
+ });
277
+ }
278
+
279
+ if (action === "member-info") {
280
+ const userId = readStringParam(params, "userId", { required: true });
281
+ return await dispatch({
282
+ action: "memberInfo",
283
+ userId,
284
+ roomId: readStringParam(params, "roomId") ?? readStringParam(params, "channelId"),
285
+ });
286
+ }
287
+
288
+ if (action === "channel-info") {
289
+ return await dispatch({
290
+ action: "channelInfo",
291
+ roomId: resolveRoomId(),
292
+ });
293
+ }
294
+
295
+ if (action === "permissions") {
296
+ const operation = normalizeLowercaseStringOrEmpty(
297
+ readStringParam(params, "operation") ??
298
+ readStringParam(params, "mode") ??
299
+ "verification-list",
300
+ );
301
+ const operationToAction: Record<string, string> = {
302
+ "encryption-status": "encryptionStatus",
303
+ "verification-status": "verificationStatus",
304
+ "verification-bootstrap": "verificationBootstrap",
305
+ "verification-recovery-key": "verificationRecoveryKey",
306
+ "verification-backup-status": "verificationBackupStatus",
307
+ "verification-backup-restore": "verificationBackupRestore",
308
+ "verification-list": "verificationList",
309
+ "verification-request": "verificationRequest",
310
+ "verification-accept": "verificationAccept",
311
+ "verification-cancel": "verificationCancel",
312
+ "verification-start": "verificationStart",
313
+ "verification-generate-qr": "verificationGenerateQr",
314
+ "verification-scan-qr": "verificationScanQr",
315
+ "verification-sas": "verificationSas",
316
+ "verification-confirm": "verificationConfirm",
317
+ "verification-mismatch": "verificationMismatch",
318
+ "verification-confirm-qr": "verificationConfirmQr",
319
+ };
320
+ const resolvedAction = operationToAction[operation];
321
+ if (!resolvedAction) {
322
+ throw new Error(
323
+ `Unsupported Matrix permissions operation: ${operation}. Supported values: ${Object.keys(
324
+ operationToAction,
325
+ ).join(", ")}`,
326
+ );
327
+ }
328
+ return await dispatch({
329
+ ...params,
330
+ action: resolvedAction,
331
+ });
332
+ }
333
+
334
+ throw new Error(`Action ${action} is not supported for provider matrix.`);
335
+ },
336
+ };
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { matrixApprovalAuth } from "./approval-auth.js";
3
+
4
+ describe("matrixApprovalAuth", () => {
5
+ it("normalizes Matrix user ids before authorizing", () => {
6
+ const cfg = {
7
+ channels: {
8
+ matrix: {
9
+ dm: { allowFrom: ["matrix:@Owner:Example.org"] },
10
+ },
11
+ },
12
+ };
13
+
14
+ expect(
15
+ matrixApprovalAuth.authorizeActorAction({
16
+ cfg,
17
+ senderId: "@owner:example.org",
18
+ action: "approve",
19
+ approvalKind: "plugin",
20
+ }),
21
+ ).toEqual({ authorized: true });
22
+ });
23
+ });
@@ -0,0 +1,25 @@
1
+ import {
2
+ createResolvedApproverActionAuthAdapter,
3
+ resolveApprovalApprovers,
4
+ } from "openclaw/plugin-sdk/approval-auth-runtime";
5
+ import { normalizeMatrixApproverId } from "./approval-ids.js";
6
+ import { resolveMatrixAccount } from "./matrix/accounts.js";
7
+ import type { CoreConfig } from "./types.js";
8
+
9
+ export function getMatrixApprovalAuthApprovers(params: {
10
+ cfg: CoreConfig;
11
+ accountId?: string | null;
12
+ }): string[] {
13
+ const account = resolveMatrixAccount(params);
14
+ return resolveApprovalApprovers({
15
+ allowFrom: account.config.dm?.allowFrom,
16
+ normalizeApprover: normalizeMatrixApproverId,
17
+ });
18
+ }
19
+
20
+ export const matrixApprovalAuth = createResolvedApproverActionAuthAdapter({
21
+ channelLabel: "Matrix",
22
+ resolveApprovers: ({ cfg, accountId }) =>
23
+ getMatrixApprovalAuthApprovers({ cfg: cfg as CoreConfig, accountId }),
24
+ normalizeSenderId: (value) => normalizeMatrixApproverId(value),
25
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { matrixApprovalNativeRuntime } from "./approval-handler.runtime.js";
3
+
4
+ describe("matrixApprovalNativeRuntime", () => {
5
+ it("uses a longer code fence when resolved commands contain triple backticks", async () => {
6
+ const result = await matrixApprovalNativeRuntime.presentation.buildResolvedResult({
7
+ cfg: {} as never,
8
+ accountId: "default",
9
+ context: {
10
+ client: {} as never,
11
+ },
12
+ request: {
13
+ id: "req-1",
14
+ request: {
15
+ command: "echo hi",
16
+ },
17
+ createdAtMs: 0,
18
+ expiresAtMs: 1_000,
19
+ },
20
+ resolved: {
21
+ id: "req-1",
22
+ decision: "allow-once",
23
+ ts: 0,
24
+ },
25
+ view: {
26
+ approvalKind: "exec",
27
+ approvalId: "req-1",
28
+ decision: "allow-once",
29
+ commandText: "echo ```danger```",
30
+ } as never,
31
+ entry: {} as never,
32
+ });
33
+
34
+ expect(result).toEqual({
35
+ kind: "update",
36
+ payload: [
37
+ "Exec approval: Allowed once",
38
+ "",
39
+ "Command",
40
+ "````",
41
+ "echo ```danger```",
42
+ "````",
43
+ ].join("\n"),
44
+ });
45
+ });
46
+ });