@openclaw/matrix 2026.3.1 → 2026.3.7
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.
- package/CHANGELOG.md +18 -0
- package/index.ts +7 -2
- package/package.json +2 -1
- package/src/actions.ts +1 -1
- package/src/channel.directory.test.ts +1 -1
- package/src/channel.ts +46 -58
- package/src/config-schema.test.ts +26 -0
- package/src/config-schema.ts +3 -2
- package/src/directory-live.ts +1 -1
- package/src/group-mentions.ts +1 -1
- package/src/matrix/accounts.ts +9 -44
- package/src/matrix/client/config.ts +55 -29
- package/src/matrix/client/create-client.ts +7 -5
- package/src/matrix/client/logging.ts +17 -7
- package/src/matrix/client/shared.ts +3 -1
- package/src/matrix/client-bootstrap.ts +2 -1
- package/src/matrix/deps.test.ts +74 -0
- package/src/matrix/deps.ts +67 -1
- package/src/matrix/monitor/access-policy.ts +6 -7
- package/src/matrix/monitor/allowlist.ts +9 -16
- package/src/matrix/monitor/auto-join.ts +3 -2
- package/src/matrix/monitor/events.test.ts +1 -1
- package/src/matrix/monitor/events.ts +1 -1
- package/src/matrix/monitor/handler.body-for-agent.test.ts +1 -1
- package/src/matrix/monitor/handler.ts +33 -36
- package/src/matrix/monitor/index.ts +6 -7
- package/src/matrix/monitor/location.ts +1 -1
- package/src/matrix/monitor/media.test.ts +1 -1
- package/src/matrix/monitor/replies.test.ts +1 -1
- package/src/matrix/monitor/replies.ts +1 -1
- package/src/matrix/monitor/rooms.ts +1 -1
- package/src/matrix/poll-types.ts +1 -1
- package/src/matrix/probe.ts +1 -1
- package/src/matrix/sdk-runtime.ts +18 -0
- package/src/matrix/send/client.ts +8 -6
- package/src/matrix/send/types.ts +1 -0
- package/src/matrix/send-queue.ts +7 -23
- package/src/matrix/send.test.ts +90 -2
- package/src/matrix/send.ts +6 -4
- package/src/onboarding.ts +52 -36
- package/src/outbound.test.ts +159 -0
- package/src/outbound.ts +7 -4
- package/src/resolve-targets.test.ts +1 -1
- package/src/resolve-targets.ts +39 -40
- package/src/runtime.ts +1 -1
- package/src/secret-input.ts +13 -0
- package/src/tool-actions.ts +1 -1
- package/src/types.ts +2 -2
package/src/matrix/send.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk/matrix";
|
|
2
2
|
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { setMatrixRuntime } from "../runtime.js";
|
|
4
4
|
|
|
@@ -24,12 +24,17 @@ vi.mock("@vector-im/matrix-bot-sdk", () => ({
|
|
|
24
24
|
RustSdkCryptoStorageProvider: vi.fn(),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
+
vi.mock("./send-queue.js", () => ({
|
|
28
|
+
enqueueSend: async <T>(_roomId: string, fn: () => Promise<T>) => await fn(),
|
|
29
|
+
}));
|
|
30
|
+
|
|
27
31
|
const loadWebMediaMock = vi.fn().mockResolvedValue({
|
|
28
32
|
buffer: Buffer.from("media"),
|
|
29
33
|
fileName: "photo.png",
|
|
30
34
|
contentType: "image/png",
|
|
31
35
|
kind: "image",
|
|
32
36
|
});
|
|
37
|
+
const runtimeLoadConfigMock = vi.fn(() => ({}));
|
|
33
38
|
const mediaKindFromMimeMock = vi.fn(() => "image");
|
|
34
39
|
const isVoiceCompatibleAudioMock = vi.fn(() => false);
|
|
35
40
|
const getImageMetadataMock = vi.fn().mockResolvedValue(null);
|
|
@@ -37,7 +42,7 @@ const resizeToJpegMock = vi.fn();
|
|
|
37
42
|
|
|
38
43
|
const runtimeStub = {
|
|
39
44
|
config: {
|
|
40
|
-
loadConfig:
|
|
45
|
+
loadConfig: runtimeLoadConfigMock,
|
|
41
46
|
},
|
|
42
47
|
media: {
|
|
43
48
|
loadWebMedia: loadWebMediaMock as unknown as PluginRuntime["media"]["loadWebMedia"],
|
|
@@ -61,6 +66,7 @@ const runtimeStub = {
|
|
|
61
66
|
} as unknown as PluginRuntime;
|
|
62
67
|
|
|
63
68
|
let sendMessageMatrix: typeof import("./send.js").sendMessageMatrix;
|
|
69
|
+
let resolveMediaMaxBytes: typeof import("./send/client.js").resolveMediaMaxBytes;
|
|
64
70
|
|
|
65
71
|
const makeClient = () => {
|
|
66
72
|
const sendMessage = vi.fn().mockResolvedValue("evt1");
|
|
@@ -76,11 +82,14 @@ const makeClient = () => {
|
|
|
76
82
|
beforeAll(async () => {
|
|
77
83
|
setMatrixRuntime(runtimeStub);
|
|
78
84
|
({ sendMessageMatrix } = await import("./send.js"));
|
|
85
|
+
({ resolveMediaMaxBytes } = await import("./send/client.js"));
|
|
79
86
|
});
|
|
80
87
|
|
|
81
88
|
describe("sendMessageMatrix media", () => {
|
|
82
89
|
beforeEach(() => {
|
|
83
90
|
vi.clearAllMocks();
|
|
91
|
+
runtimeLoadConfigMock.mockReset();
|
|
92
|
+
runtimeLoadConfigMock.mockReturnValue({});
|
|
84
93
|
mediaKindFromMimeMock.mockReturnValue("image");
|
|
85
94
|
isVoiceCompatibleAudioMock.mockReturnValue(false);
|
|
86
95
|
setMatrixRuntime(runtimeStub);
|
|
@@ -210,6 +219,8 @@ describe("sendMessageMatrix media", () => {
|
|
|
210
219
|
describe("sendMessageMatrix threads", () => {
|
|
211
220
|
beforeEach(() => {
|
|
212
221
|
vi.clearAllMocks();
|
|
222
|
+
runtimeLoadConfigMock.mockReset();
|
|
223
|
+
runtimeLoadConfigMock.mockReturnValue({});
|
|
213
224
|
setMatrixRuntime(runtimeStub);
|
|
214
225
|
});
|
|
215
226
|
|
|
@@ -236,3 +247,80 @@ describe("sendMessageMatrix threads", () => {
|
|
|
236
247
|
});
|
|
237
248
|
});
|
|
238
249
|
});
|
|
250
|
+
|
|
251
|
+
describe("sendMessageMatrix cfg threading", () => {
|
|
252
|
+
beforeEach(() => {
|
|
253
|
+
vi.clearAllMocks();
|
|
254
|
+
runtimeLoadConfigMock.mockReset();
|
|
255
|
+
runtimeLoadConfigMock.mockReturnValue({
|
|
256
|
+
channels: {
|
|
257
|
+
matrix: {
|
|
258
|
+
mediaMaxMb: 7,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
setMatrixRuntime(runtimeStub);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("does not call runtime loadConfig when cfg is provided", async () => {
|
|
266
|
+
const { client } = makeClient();
|
|
267
|
+
const providedCfg = {
|
|
268
|
+
channels: {
|
|
269
|
+
matrix: {
|
|
270
|
+
mediaMaxMb: 4,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
await sendMessageMatrix("room:!room:example", "hello cfg", {
|
|
276
|
+
client,
|
|
277
|
+
cfg: providedCfg as any,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
expect(runtimeLoadConfigMock).not.toHaveBeenCalled();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("falls back to runtime loadConfig when cfg is omitted", async () => {
|
|
284
|
+
const { client } = makeClient();
|
|
285
|
+
|
|
286
|
+
await sendMessageMatrix("room:!room:example", "hello runtime", { client });
|
|
287
|
+
|
|
288
|
+
expect(runtimeLoadConfigMock).toHaveBeenCalledTimes(1);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("resolveMediaMaxBytes cfg threading", () => {
|
|
293
|
+
beforeEach(() => {
|
|
294
|
+
runtimeLoadConfigMock.mockReset();
|
|
295
|
+
runtimeLoadConfigMock.mockReturnValue({
|
|
296
|
+
channels: {
|
|
297
|
+
matrix: {
|
|
298
|
+
mediaMaxMb: 9,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
setMatrixRuntime(runtimeStub);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("uses provided cfg and skips runtime loadConfig", () => {
|
|
306
|
+
const providedCfg = {
|
|
307
|
+
channels: {
|
|
308
|
+
matrix: {
|
|
309
|
+
mediaMaxMb: 3,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const maxBytes = resolveMediaMaxBytes(undefined, providedCfg as any);
|
|
315
|
+
|
|
316
|
+
expect(maxBytes).toBe(3 * 1024 * 1024);
|
|
317
|
+
expect(runtimeLoadConfigMock).not.toHaveBeenCalled();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("falls back to runtime loadConfig when cfg is omitted", () => {
|
|
321
|
+
const maxBytes = resolveMediaMaxBytes();
|
|
322
|
+
|
|
323
|
+
expect(maxBytes).toBe(9 * 1024 * 1024);
|
|
324
|
+
expect(runtimeLoadConfigMock).toHaveBeenCalledTimes(1);
|
|
325
|
+
});
|
|
326
|
+
});
|
package/src/matrix/send.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
|
|
2
|
-
import type { PollInput } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { PollInput } from "openclaw/plugin-sdk/matrix";
|
|
3
3
|
import { getMatrixRuntime } from "../runtime.js";
|
|
4
4
|
import { buildPollStartContent, M_POLL_START } from "./poll-types.js";
|
|
5
5
|
import { enqueueSend } from "./send-queue.js";
|
|
@@ -47,11 +47,12 @@ export async function sendMessageMatrix(
|
|
|
47
47
|
client: opts.client,
|
|
48
48
|
timeoutMs: opts.timeoutMs,
|
|
49
49
|
accountId: opts.accountId,
|
|
50
|
+
cfg: opts.cfg,
|
|
50
51
|
});
|
|
52
|
+
const cfg = opts.cfg ?? getCore().config.loadConfig();
|
|
51
53
|
try {
|
|
52
54
|
const roomId = await resolveMatrixRoomId(client, to);
|
|
53
55
|
return await enqueueSend(roomId, async () => {
|
|
54
|
-
const cfg = getCore().config.loadConfig();
|
|
55
56
|
const tableMode = getCore().channel.text.resolveMarkdownTableMode({
|
|
56
57
|
cfg,
|
|
57
58
|
channel: "matrix",
|
|
@@ -81,7 +82,7 @@ export async function sendMessageMatrix(
|
|
|
81
82
|
|
|
82
83
|
let lastMessageId = "";
|
|
83
84
|
if (opts.mediaUrl) {
|
|
84
|
-
const maxBytes = resolveMediaMaxBytes(opts.accountId);
|
|
85
|
+
const maxBytes = resolveMediaMaxBytes(opts.accountId, cfg);
|
|
85
86
|
const media = await getCore().media.loadWebMedia(opts.mediaUrl, maxBytes);
|
|
86
87
|
const uploaded = await uploadMediaMaybeEncrypted(client, roomId, media.buffer, {
|
|
87
88
|
contentType: media.contentType,
|
|
@@ -91,7 +92,7 @@ export async function sendMessageMatrix(
|
|
|
91
92
|
buffer: media.buffer,
|
|
92
93
|
contentType: media.contentType,
|
|
93
94
|
fileName: media.fileName,
|
|
94
|
-
kind: media.kind,
|
|
95
|
+
kind: media.kind ?? "unknown",
|
|
95
96
|
});
|
|
96
97
|
const baseMsgType = resolveMatrixMsgType(media.contentType, media.fileName);
|
|
97
98
|
const { useVoice } = resolveMatrixVoiceDecision({
|
|
@@ -171,6 +172,7 @@ export async function sendPollMatrix(
|
|
|
171
172
|
client: opts.client,
|
|
172
173
|
timeoutMs: opts.timeoutMs,
|
|
173
174
|
accountId: opts.accountId,
|
|
175
|
+
cfg: opts.cfg,
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
try {
|
package/src/onboarding.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import type { DmPolicy } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { DmPolicy } from "openclaw/plugin-sdk/matrix";
|
|
2
2
|
import {
|
|
3
3
|
addWildcardAllowFrom,
|
|
4
|
+
buildSingleChannelSecretPromptState,
|
|
5
|
+
formatResolvedUnresolvedNote,
|
|
4
6
|
formatDocsLink,
|
|
7
|
+
hasConfiguredSecretInput,
|
|
5
8
|
mergeAllowFromEntries,
|
|
9
|
+
promptSingleChannelSecretInput,
|
|
6
10
|
promptChannelAccessConfig,
|
|
11
|
+
setTopLevelChannelGroupPolicy,
|
|
12
|
+
type SecretInput,
|
|
7
13
|
type ChannelOnboardingAdapter,
|
|
8
14
|
type ChannelOnboardingDmPolicy,
|
|
9
15
|
type WizardPrompter,
|
|
10
|
-
} from "openclaw/plugin-sdk";
|
|
16
|
+
} from "openclaw/plugin-sdk/matrix";
|
|
11
17
|
import { listMatrixDirectoryGroupsLive } from "./directory-live.js";
|
|
12
18
|
import { resolveMatrixAccount } from "./matrix/accounts.js";
|
|
13
19
|
import { ensureMatrixSdkInstalled, isMatrixSdkAvailable } from "./matrix/deps.js";
|
|
@@ -139,17 +145,12 @@ async function promptMatrixAllowFrom(params: {
|
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
function setMatrixGroupPolicy(cfg: CoreConfig, groupPolicy: "open" | "allowlist" | "disabled") {
|
|
142
|
-
return {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
enabled: true,
|
|
149
|
-
groupPolicy,
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
};
|
|
148
|
+
return setTopLevelChannelGroupPolicy({
|
|
149
|
+
cfg,
|
|
150
|
+
channel: "matrix",
|
|
151
|
+
groupPolicy,
|
|
152
|
+
enabled: true,
|
|
153
|
+
}) as CoreConfig;
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
function setMatrixGroupRooms(cfg: CoreConfig, roomKeys: string[]) {
|
|
@@ -265,22 +266,24 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
265
266
|
).trim();
|
|
266
267
|
|
|
267
268
|
let accessToken = existing.accessToken ?? "";
|
|
268
|
-
let password = existing.password
|
|
269
|
+
let password: SecretInput | undefined = existing.password;
|
|
269
270
|
let userId = existing.userId ?? "";
|
|
271
|
+
const existingPasswordConfigured = hasConfiguredSecretInput(existing.password);
|
|
272
|
+
const passwordConfigured = () => hasConfiguredSecretInput(password);
|
|
270
273
|
|
|
271
|
-
if (accessToken ||
|
|
274
|
+
if (accessToken || passwordConfigured()) {
|
|
272
275
|
const keep = await prompter.confirm({
|
|
273
276
|
message: "Matrix credentials already configured. Keep them?",
|
|
274
277
|
initialValue: true,
|
|
275
278
|
});
|
|
276
279
|
if (!keep) {
|
|
277
280
|
accessToken = "";
|
|
278
|
-
password =
|
|
281
|
+
password = undefined;
|
|
279
282
|
userId = "";
|
|
280
283
|
}
|
|
281
284
|
}
|
|
282
285
|
|
|
283
|
-
if (!accessToken && !
|
|
286
|
+
if (!accessToken && !passwordConfigured()) {
|
|
284
287
|
// Ask auth method FIRST before asking for user ID
|
|
285
288
|
const authMode = await prompter.select({
|
|
286
289
|
message: "Matrix auth method",
|
|
@@ -321,12 +324,31 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
321
324
|
},
|
|
322
325
|
}),
|
|
323
326
|
).trim();
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
)
|
|
327
|
+
const passwordPromptState = buildSingleChannelSecretPromptState({
|
|
328
|
+
accountConfigured: Boolean(existingPasswordConfigured),
|
|
329
|
+
hasConfigToken: existingPasswordConfigured,
|
|
330
|
+
allowEnv: true,
|
|
331
|
+
envValue: envPassword,
|
|
332
|
+
});
|
|
333
|
+
const passwordResult = await promptSingleChannelSecretInput({
|
|
334
|
+
cfg: next,
|
|
335
|
+
prompter,
|
|
336
|
+
providerHint: "matrix",
|
|
337
|
+
credentialLabel: "password",
|
|
338
|
+
accountConfigured: passwordPromptState.accountConfigured,
|
|
339
|
+
canUseEnv: passwordPromptState.canUseEnv,
|
|
340
|
+
hasConfigToken: passwordPromptState.hasConfigToken,
|
|
341
|
+
envPrompt: "MATRIX_PASSWORD detected. Use env var?",
|
|
342
|
+
keepPrompt: "Matrix password already configured. Keep it?",
|
|
343
|
+
inputPrompt: "Matrix password",
|
|
344
|
+
preferredEnvVar: "MATRIX_PASSWORD",
|
|
345
|
+
});
|
|
346
|
+
if (passwordResult.action === "set") {
|
|
347
|
+
password = passwordResult.value;
|
|
348
|
+
}
|
|
349
|
+
if (passwordResult.action === "use-env") {
|
|
350
|
+
password = undefined;
|
|
351
|
+
}
|
|
330
352
|
}
|
|
331
353
|
}
|
|
332
354
|
|
|
@@ -353,7 +375,7 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
353
375
|
homeserver,
|
|
354
376
|
userId: userId || undefined,
|
|
355
377
|
accessToken: accessToken || undefined,
|
|
356
|
-
password: password
|
|
378
|
+
password: password,
|
|
357
379
|
deviceName: deviceName || undefined,
|
|
358
380
|
encryption: enableEncryption || undefined,
|
|
359
381
|
},
|
|
@@ -408,18 +430,12 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
408
430
|
}
|
|
409
431
|
}
|
|
410
432
|
roomKeys = [...resolvedIds, ...unresolved.map((entry) => entry.trim()).filter(Boolean)];
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
: undefined,
|
|
418
|
-
]
|
|
419
|
-
.filter(Boolean)
|
|
420
|
-
.join("\n"),
|
|
421
|
-
"Matrix rooms",
|
|
422
|
-
);
|
|
433
|
+
const resolution = formatResolvedUnresolvedNote({
|
|
434
|
+
resolved: resolvedIds,
|
|
435
|
+
unresolved,
|
|
436
|
+
});
|
|
437
|
+
if (resolution) {
|
|
438
|
+
await prompter.note(resolution, "Matrix rooms");
|
|
423
439
|
}
|
|
424
440
|
} catch (err) {
|
|
425
441
|
await prompter.note(
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/matrix";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
sendMessageMatrix: vi.fn(),
|
|
6
|
+
sendPollMatrix: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock("./matrix/send.js", () => ({
|
|
10
|
+
sendMessageMatrix: mocks.sendMessageMatrix,
|
|
11
|
+
sendPollMatrix: mocks.sendPollMatrix,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock("./runtime.js", () => ({
|
|
15
|
+
getMatrixRuntime: () => ({
|
|
16
|
+
channel: {
|
|
17
|
+
text: {
|
|
18
|
+
chunkMarkdownText: (text: string) => [text],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
import { matrixOutbound } from "./outbound.js";
|
|
25
|
+
|
|
26
|
+
describe("matrixOutbound cfg threading", () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
mocks.sendMessageMatrix.mockReset();
|
|
29
|
+
mocks.sendPollMatrix.mockReset();
|
|
30
|
+
mocks.sendMessageMatrix.mockResolvedValue({ messageId: "evt-1", roomId: "!room:example" });
|
|
31
|
+
mocks.sendPollMatrix.mockResolvedValue({ eventId: "$poll", roomId: "!room:example" });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("passes resolved cfg to sendMessageMatrix for text sends", async () => {
|
|
35
|
+
const cfg = {
|
|
36
|
+
channels: {
|
|
37
|
+
matrix: {
|
|
38
|
+
accessToken: "resolved-token",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
} as OpenClawConfig;
|
|
42
|
+
|
|
43
|
+
await matrixOutbound.sendText!({
|
|
44
|
+
cfg,
|
|
45
|
+
to: "room:!room:example",
|
|
46
|
+
text: "hello",
|
|
47
|
+
accountId: "default",
|
|
48
|
+
threadId: "$thread",
|
|
49
|
+
replyToId: "$reply",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(mocks.sendMessageMatrix).toHaveBeenCalledWith(
|
|
53
|
+
"room:!room:example",
|
|
54
|
+
"hello",
|
|
55
|
+
expect.objectContaining({
|
|
56
|
+
cfg,
|
|
57
|
+
accountId: "default",
|
|
58
|
+
threadId: "$thread",
|
|
59
|
+
replyToId: "$reply",
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("passes resolved cfg to sendMessageMatrix for media sends", async () => {
|
|
65
|
+
const cfg = {
|
|
66
|
+
channels: {
|
|
67
|
+
matrix: {
|
|
68
|
+
accessToken: "resolved-token",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
} as OpenClawConfig;
|
|
72
|
+
|
|
73
|
+
await matrixOutbound.sendMedia!({
|
|
74
|
+
cfg,
|
|
75
|
+
to: "room:!room:example",
|
|
76
|
+
text: "caption",
|
|
77
|
+
mediaUrl: "file:///tmp/cat.png",
|
|
78
|
+
accountId: "default",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(mocks.sendMessageMatrix).toHaveBeenCalledWith(
|
|
82
|
+
"room:!room:example",
|
|
83
|
+
"caption",
|
|
84
|
+
expect.objectContaining({
|
|
85
|
+
cfg,
|
|
86
|
+
mediaUrl: "file:///tmp/cat.png",
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("passes resolved cfg through injected deps.sendMatrix", async () => {
|
|
92
|
+
const cfg = {
|
|
93
|
+
channels: {
|
|
94
|
+
matrix: {
|
|
95
|
+
accessToken: "resolved-token",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
} as OpenClawConfig;
|
|
99
|
+
const sendMatrix = vi.fn(async () => ({
|
|
100
|
+
messageId: "evt-injected",
|
|
101
|
+
roomId: "!room:example",
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
await matrixOutbound.sendText!({
|
|
105
|
+
cfg,
|
|
106
|
+
to: "room:!room:example",
|
|
107
|
+
text: "hello via deps",
|
|
108
|
+
deps: { sendMatrix },
|
|
109
|
+
accountId: "default",
|
|
110
|
+
threadId: "$thread",
|
|
111
|
+
replyToId: "$reply",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(sendMatrix).toHaveBeenCalledWith(
|
|
115
|
+
"room:!room:example",
|
|
116
|
+
"hello via deps",
|
|
117
|
+
expect.objectContaining({
|
|
118
|
+
cfg,
|
|
119
|
+
accountId: "default",
|
|
120
|
+
threadId: "$thread",
|
|
121
|
+
replyToId: "$reply",
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("passes resolved cfg to sendPollMatrix", async () => {
|
|
127
|
+
const cfg = {
|
|
128
|
+
channels: {
|
|
129
|
+
matrix: {
|
|
130
|
+
accessToken: "resolved-token",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
} as OpenClawConfig;
|
|
134
|
+
|
|
135
|
+
await matrixOutbound.sendPoll!({
|
|
136
|
+
cfg,
|
|
137
|
+
to: "room:!room:example",
|
|
138
|
+
poll: {
|
|
139
|
+
question: "Snack?",
|
|
140
|
+
options: ["Pizza", "Sushi"],
|
|
141
|
+
},
|
|
142
|
+
accountId: "default",
|
|
143
|
+
threadId: "$thread",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(mocks.sendPollMatrix).toHaveBeenCalledWith(
|
|
147
|
+
"room:!room:example",
|
|
148
|
+
expect.objectContaining({
|
|
149
|
+
question: "Snack?",
|
|
150
|
+
options: ["Pizza", "Sushi"],
|
|
151
|
+
}),
|
|
152
|
+
expect.objectContaining({
|
|
153
|
+
cfg,
|
|
154
|
+
accountId: "default",
|
|
155
|
+
threadId: "$thread",
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
});
|
package/src/outbound.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/matrix";
|
|
2
2
|
import { sendMessageMatrix, sendPollMatrix } from "./matrix/send.js";
|
|
3
3
|
import { getMatrixRuntime } from "./runtime.js";
|
|
4
4
|
|
|
@@ -7,11 +7,12 @@ export const matrixOutbound: ChannelOutboundAdapter = {
|
|
|
7
7
|
chunker: (text, limit) => getMatrixRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
8
8
|
chunkerMode: "markdown",
|
|
9
9
|
textChunkLimit: 4000,
|
|
10
|
-
sendText: async ({ to, text, deps, replyToId, threadId, accountId }) => {
|
|
10
|
+
sendText: async ({ cfg, to, text, deps, replyToId, threadId, accountId }) => {
|
|
11
11
|
const send = deps?.sendMatrix ?? sendMessageMatrix;
|
|
12
12
|
const resolvedThreadId =
|
|
13
13
|
threadId !== undefined && threadId !== null ? String(threadId) : undefined;
|
|
14
14
|
const result = await send(to, text, {
|
|
15
|
+
cfg,
|
|
15
16
|
replyToId: replyToId ?? undefined,
|
|
16
17
|
threadId: resolvedThreadId,
|
|
17
18
|
accountId: accountId ?? undefined,
|
|
@@ -22,11 +23,12 @@ export const matrixOutbound: ChannelOutboundAdapter = {
|
|
|
22
23
|
roomId: result.roomId,
|
|
23
24
|
};
|
|
24
25
|
},
|
|
25
|
-
sendMedia: async ({ to, text, mediaUrl, deps, replyToId, threadId, accountId }) => {
|
|
26
|
+
sendMedia: async ({ cfg, to, text, mediaUrl, deps, replyToId, threadId, accountId }) => {
|
|
26
27
|
const send = deps?.sendMatrix ?? sendMessageMatrix;
|
|
27
28
|
const resolvedThreadId =
|
|
28
29
|
threadId !== undefined && threadId !== null ? String(threadId) : undefined;
|
|
29
30
|
const result = await send(to, text, {
|
|
31
|
+
cfg,
|
|
30
32
|
mediaUrl,
|
|
31
33
|
replyToId: replyToId ?? undefined,
|
|
32
34
|
threadId: resolvedThreadId,
|
|
@@ -38,10 +40,11 @@ export const matrixOutbound: ChannelOutboundAdapter = {
|
|
|
38
40
|
roomId: result.roomId,
|
|
39
41
|
};
|
|
40
42
|
},
|
|
41
|
-
sendPoll: async ({ to, poll, threadId, accountId }) => {
|
|
43
|
+
sendPoll: async ({ cfg, to, poll, threadId, accountId }) => {
|
|
42
44
|
const resolvedThreadId =
|
|
43
45
|
threadId !== undefined && threadId !== null ? String(threadId) : undefined;
|
|
44
46
|
const result = await sendPollMatrix(to, poll, {
|
|
47
|
+
cfg,
|
|
45
48
|
threadId: resolvedThreadId,
|
|
46
49
|
accountId: accountId ?? undefined,
|
|
47
50
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk/matrix";
|
|
2
2
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
3
3
|
import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./directory-live.js";
|
|
4
4
|
import { resolveMatrixTargets } from "./resolve-targets.js";
|
package/src/resolve-targets.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/compat";
|
|
1
2
|
import type {
|
|
2
3
|
ChannelDirectoryEntry,
|
|
3
4
|
ChannelResolveKind,
|
|
4
5
|
ChannelResolveResult,
|
|
5
6
|
RuntimeEnv,
|
|
6
|
-
} from "openclaw/plugin-sdk";
|
|
7
|
+
} from "openclaw/plugin-sdk/matrix";
|
|
7
8
|
import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./directory-live.js";
|
|
8
9
|
|
|
9
10
|
function findExactDirectoryMatches(
|
|
@@ -71,56 +72,54 @@ export async function resolveMatrixTargets(params: {
|
|
|
71
72
|
kind: ChannelResolveKind;
|
|
72
73
|
runtime?: RuntimeEnv;
|
|
73
74
|
}): Promise<ChannelResolveResult[]> {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
return await mapAllowlistResolutionInputs({
|
|
76
|
+
inputs: params.inputs,
|
|
77
|
+
mapInput: async (input): Promise<ChannelResolveResult> => {
|
|
78
|
+
const trimmed = input.trim();
|
|
79
|
+
if (!trimmed) {
|
|
80
|
+
return { input, resolved: false, note: "empty input" };
|
|
81
|
+
}
|
|
82
|
+
if (params.kind === "user") {
|
|
83
|
+
if (trimmed.startsWith("@") && trimmed.includes(":")) {
|
|
84
|
+
return { input, resolved: true, id: trimmed };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const matches = await listMatrixDirectoryPeersLive({
|
|
88
|
+
cfg: params.cfg,
|
|
89
|
+
query: trimmed,
|
|
90
|
+
limit: 5,
|
|
91
|
+
});
|
|
92
|
+
const best = pickBestUserMatch(matches, trimmed);
|
|
93
|
+
return {
|
|
94
|
+
input,
|
|
95
|
+
resolved: Boolean(best?.id),
|
|
96
|
+
id: best?.id,
|
|
97
|
+
name: best?.name,
|
|
98
|
+
note: best ? undefined : describeUserMatchFailure(matches, trimmed),
|
|
99
|
+
};
|
|
100
|
+
} catch (err) {
|
|
101
|
+
params.runtime?.error?.(`matrix resolve failed: ${String(err)}`);
|
|
102
|
+
return { input, resolved: false, note: "lookup failed" };
|
|
103
|
+
}
|
|
85
104
|
}
|
|
86
105
|
try {
|
|
87
|
-
const matches = await
|
|
106
|
+
const matches = await listMatrixDirectoryGroupsLive({
|
|
88
107
|
cfg: params.cfg,
|
|
89
108
|
query: trimmed,
|
|
90
109
|
limit: 5,
|
|
91
110
|
});
|
|
92
|
-
const best =
|
|
93
|
-
|
|
111
|
+
const best = pickBestGroupMatch(matches, trimmed);
|
|
112
|
+
return {
|
|
94
113
|
input,
|
|
95
114
|
resolved: Boolean(best?.id),
|
|
96
115
|
id: best?.id,
|
|
97
116
|
name: best?.name,
|
|
98
|
-
note:
|
|
99
|
-
}
|
|
117
|
+
note: matches.length > 1 ? "multiple matches; chose first" : undefined,
|
|
118
|
+
};
|
|
100
119
|
} catch (err) {
|
|
101
120
|
params.runtime?.error?.(`matrix resolve failed: ${String(err)}`);
|
|
102
|
-
|
|
121
|
+
return { input, resolved: false, note: "lookup failed" };
|
|
103
122
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const matches = await listMatrixDirectoryGroupsLive({
|
|
108
|
-
cfg: params.cfg,
|
|
109
|
-
query: trimmed,
|
|
110
|
-
limit: 5,
|
|
111
|
-
});
|
|
112
|
-
const best = pickBestGroupMatch(matches, trimmed);
|
|
113
|
-
results.push({
|
|
114
|
-
input,
|
|
115
|
-
resolved: Boolean(best?.id),
|
|
116
|
-
id: best?.id,
|
|
117
|
-
name: best?.name,
|
|
118
|
-
note: matches.length > 1 ? "multiple matches; chose first" : undefined,
|
|
119
|
-
});
|
|
120
|
-
} catch (err) {
|
|
121
|
-
params.runtime?.error?.(`matrix resolve failed: ${String(err)}`);
|
|
122
|
-
results.push({ input, resolved: false, note: "lookup failed" });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return results;
|
|
123
|
+
},
|
|
124
|
+
});
|
|
126
125
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSecretInputSchema,
|
|
3
|
+
hasConfiguredSecretInput,
|
|
4
|
+
normalizeResolvedSecretInputString,
|
|
5
|
+
normalizeSecretInputString,
|
|
6
|
+
} from "openclaw/plugin-sdk/matrix";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
buildSecretInputSchema,
|
|
10
|
+
hasConfiguredSecretInput,
|
|
11
|
+
normalizeResolvedSecretInputString,
|
|
12
|
+
normalizeSecretInputString,
|
|
13
|
+
};
|