Skip to content

Commit 94693f7

Browse files
committed
Matrix: rebuild plugin migration branch
1 parent 513b486 commit 94693f7

File tree

244 files changed

+38982
-5810
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

244 files changed

+38982
-5810
lines changed

docs/channels/matrix.md

Lines changed: 513 additions & 205 deletions
Large diffs are not rendered by default.

docs/install/migrating-matrix.md

Lines changed: 344 additions & 0 deletions
Large diffs are not rendered by default.

extensions/matrix/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./src/setup-core.js";
22
export * from "./src/setup-surface.js";
3+
export { matrixOnboardingAdapter as matrixSetupWizard } from "./src/onboarding.js";

extensions/matrix/helper-api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "./src/account-selection.js";
2+
export * from "./src/env-vars.js";
3+
export * from "./src/storage-paths.js";

extensions/matrix/index.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
22
import { matrixPlugin } from "./src/channel.js";
3+
import { registerMatrixCli } from "./src/cli.js";
34
import { setMatrixRuntime } from "./src/runtime.js";
45

56
export { matrixPlugin } from "./src/channel.js";
@@ -8,7 +9,42 @@ export { setMatrixRuntime } from "./src/runtime.js";
89
export default defineChannelPluginEntry({
910
id: "matrix",
1011
name: "Matrix",
11-
description: "Matrix channel plugin",
12+
description: "Matrix channel plugin (matrix-js-sdk)",
1213
plugin: matrixPlugin,
1314
setRuntime: setMatrixRuntime,
15+
registerFull(api) {
16+
void import("./src/plugin-entry.runtime.js")
17+
.then(({ ensureMatrixCryptoRuntime }) =>
18+
ensureMatrixCryptoRuntime({ log: api.logger.info }).catch((err: unknown) => {
19+
const message = err instanceof Error ? err.message : String(err);
20+
api.logger.warn?.(`matrix: crypto runtime bootstrap failed: ${message}`);
21+
}),
22+
)
23+
.catch((err: unknown) => {
24+
const message = err instanceof Error ? err.message : String(err);
25+
api.logger.warn?.(`matrix: failed loading crypto bootstrap runtime: ${message}`);
26+
});
27+
28+
api.registerGatewayMethod("matrix.verify.recoveryKey", async (ctx) => {
29+
const { handleVerifyRecoveryKey } = await import("./src/plugin-entry.runtime.js");
30+
await handleVerifyRecoveryKey(ctx);
31+
});
32+
33+
api.registerGatewayMethod("matrix.verify.bootstrap", async (ctx) => {
34+
const { handleVerificationBootstrap } = await import("./src/plugin-entry.runtime.js");
35+
await handleVerificationBootstrap(ctx);
36+
});
37+
38+
api.registerGatewayMethod("matrix.verify.status", async (ctx) => {
39+
const { handleVerificationStatus } = await import("./src/plugin-entry.runtime.js");
40+
await handleVerificationStatus(ctx);
41+
});
42+
43+
api.registerCli(
44+
({ program }) => {
45+
registerMatrixCli({ program });
46+
},
47+
{ commands: ["matrix"] },
48+
);
49+
},
1450
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type { MatrixLegacyCryptoInspectionResult } from "./src/matrix/legacy-crypto-inspector.js";
2+
export { inspectLegacyMatrixCryptoStore } from "./src/matrix/legacy-crypto-inspector.js";

extensions/matrix/package.json

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
{
22
"name": "@openclaw/matrix",
3-
"version": "2026.3.14",
3+
"version": "2026.3.11",
44
"description": "OpenClaw Matrix channel plugin",
55
"type": "module",
66
"dependencies": {
7-
"@mariozechner/pi-agent-core": "0.60.0",
87
"@matrix-org/matrix-sdk-crypto-nodejs": "^0.4.0",
9-
"@vector-im/matrix-bot-sdk": "0.8.0-element.3",
10-
"markdown-it": "14.1.1",
11-
"music-metadata": "^11.12.3",
8+
"fake-indexeddb": "^6.2.5",
9+
"markdown-it": "14.1.0",
10+
"matrix-js-sdk": "^40.1.0",
11+
"music-metadata": "^11.11.2",
1212
"zod": "^4.3.6"
1313
},
14+
"devDependencies": {
15+
"openclaw": "workspace:*"
16+
},
1417
"openclaw": {
1518
"extensions": [
1619
"./index.ts"
@@ -31,8 +34,12 @@
3134
"localPath": "extensions/matrix",
3235
"defaultChoice": "npm"
3336
},
34-
"release": {
35-
"publishToNpm": true
37+
"releaseChecks": {
38+
"rootDependencyMirrorAllowlist": [
39+
"@matrix-org/matrix-sdk-crypto-nodejs",
40+
"matrix-js-sdk",
41+
"music-metadata"
42+
]
3643
}
3744
}
3845
}

extensions/matrix/runtime-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from "openclaw/plugin-sdk/matrix";
2+
export * from "./src/auth-precedence.js";
3+
export * from "./helper-api.js";
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {
2+
DEFAULT_ACCOUNT_ID,
3+
normalizeAccountId,
4+
normalizeOptionalAccountId,
5+
} from "openclaw/plugin-sdk/account-id";
6+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
7+
import { listMatrixEnvAccountIds } from "./env-vars.js";
8+
9+
function isRecord(value: unknown): value is Record<string, unknown> {
10+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
11+
}
12+
13+
export function resolveMatrixChannelConfig(cfg: OpenClawConfig): Record<string, unknown> | null {
14+
return isRecord(cfg.channels?.matrix) ? cfg.channels.matrix : null;
15+
}
16+
17+
export function findMatrixAccountEntry(
18+
cfg: OpenClawConfig,
19+
accountId: string,
20+
): Record<string, unknown> | null {
21+
const channel = resolveMatrixChannelConfig(cfg);
22+
if (!channel) {
23+
return null;
24+
}
25+
26+
const accounts = isRecord(channel.accounts) ? channel.accounts : null;
27+
if (!accounts) {
28+
return null;
29+
}
30+
31+
const normalizedAccountId = normalizeAccountId(accountId);
32+
for (const [rawAccountId, value] of Object.entries(accounts)) {
33+
if (normalizeAccountId(rawAccountId) === normalizedAccountId && isRecord(value)) {
34+
return value;
35+
}
36+
}
37+
38+
return null;
39+
}
40+
41+
export function resolveConfiguredMatrixAccountIds(
42+
cfg: OpenClawConfig,
43+
env: NodeJS.ProcessEnv = process.env,
44+
): string[] {
45+
const channel = resolveMatrixChannelConfig(cfg);
46+
const ids = new Set<string>(listMatrixEnvAccountIds(env));
47+
48+
const accounts = channel && isRecord(channel.accounts) ? channel.accounts : null;
49+
if (accounts) {
50+
for (const [accountId, value] of Object.entries(accounts)) {
51+
if (isRecord(value)) {
52+
ids.add(normalizeAccountId(accountId));
53+
}
54+
}
55+
}
56+
57+
if (ids.size === 0 && channel) {
58+
ids.add(DEFAULT_ACCOUNT_ID);
59+
}
60+
61+
return Array.from(ids).toSorted((a, b) => a.localeCompare(b));
62+
}
63+
64+
export function resolveMatrixDefaultOrOnlyAccountId(
65+
cfg: OpenClawConfig,
66+
env: NodeJS.ProcessEnv = process.env,
67+
): string {
68+
const channel = resolveMatrixChannelConfig(cfg);
69+
if (!channel) {
70+
return DEFAULT_ACCOUNT_ID;
71+
}
72+
73+
const configuredDefault = normalizeOptionalAccountId(
74+
typeof channel.defaultAccount === "string" ? channel.defaultAccount : undefined,
75+
);
76+
const configuredAccountIds = resolveConfiguredMatrixAccountIds(cfg, env);
77+
if (configuredDefault && configuredAccountIds.includes(configuredDefault)) {
78+
return configuredDefault;
79+
}
80+
if (configuredAccountIds.includes(DEFAULT_ACCOUNT_ID)) {
81+
return DEFAULT_ACCOUNT_ID;
82+
}
83+
84+
if (configuredAccountIds.length === 1) {
85+
return configuredAccountIds[0] ?? DEFAULT_ACCOUNT_ID;
86+
}
87+
return DEFAULT_ACCOUNT_ID;
88+
}
89+
90+
export function requiresExplicitMatrixDefaultAccount(
91+
cfg: OpenClawConfig,
92+
env: NodeJS.ProcessEnv = process.env,
93+
): boolean {
94+
const channel = resolveMatrixChannelConfig(cfg);
95+
if (!channel) {
96+
return false;
97+
}
98+
const configuredAccountIds = resolveConfiguredMatrixAccountIds(cfg, env);
99+
if (configuredAccountIds.length <= 1) {
100+
return false;
101+
}
102+
const configuredDefault = normalizeOptionalAccountId(
103+
typeof channel.defaultAccount === "string" ? channel.defaultAccount : undefined,
104+
);
105+
return !(configuredDefault && configuredAccountIds.includes(configuredDefault));
106+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import type { ChannelMessageActionContext } from "openclaw/plugin-sdk/matrix";
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
3+
import type { CoreConfig } from "./types.js";
4+
5+
const mocks = vi.hoisted(() => ({
6+
handleMatrixAction: vi.fn(),
7+
}));
8+
9+
vi.mock("./tool-actions.js", () => ({
10+
handleMatrixAction: mocks.handleMatrixAction,
11+
}));
12+
13+
const { matrixMessageActions } = await import("./actions.js");
14+
15+
function createContext(
16+
overrides: Partial<ChannelMessageActionContext>,
17+
): ChannelMessageActionContext {
18+
return {
19+
channel: "matrix",
20+
action: "send",
21+
cfg: {
22+
channels: {
23+
matrix: {
24+
enabled: true,
25+
homeserver: "https://matrix.example.org",
26+
userId: "@bot:example.org",
27+
accessToken: "token",
28+
},
29+
},
30+
} as CoreConfig,
31+
params: {},
32+
...overrides,
33+
};
34+
}
35+
36+
describe("matrixMessageActions account propagation", () => {
37+
beforeEach(() => {
38+
mocks.handleMatrixAction.mockReset().mockResolvedValue({
39+
ok: true,
40+
output: "",
41+
details: { ok: true },
42+
});
43+
});
44+
45+
it("forwards accountId for send actions", async () => {
46+
await matrixMessageActions.handleAction?.(
47+
createContext({
48+
action: "send",
49+
accountId: "ops",
50+
params: {
51+
to: "room:!room:example",
52+
message: "hello",
53+
},
54+
}),
55+
);
56+
57+
expect(mocks.handleMatrixAction).toHaveBeenCalledWith(
58+
expect.objectContaining({
59+
action: "sendMessage",
60+
accountId: "ops",
61+
}),
62+
expect.any(Object),
63+
{ mediaLocalRoots: undefined },
64+
);
65+
});
66+
67+
it("forwards accountId for permissions actions", async () => {
68+
await matrixMessageActions.handleAction?.(
69+
createContext({
70+
action: "permissions",
71+
accountId: "ops",
72+
params: {
73+
operation: "verification-list",
74+
},
75+
}),
76+
);
77+
78+
expect(mocks.handleMatrixAction).toHaveBeenCalledWith(
79+
expect.objectContaining({
80+
action: "verificationList",
81+
accountId: "ops",
82+
}),
83+
expect.any(Object),
84+
{ mediaLocalRoots: undefined },
85+
);
86+
});
87+
88+
it("forwards accountId for self-profile updates", async () => {
89+
await matrixMessageActions.handleAction?.(
90+
createContext({
91+
action: "set-profile",
92+
accountId: "ops",
93+
params: {
94+
displayName: "Ops Bot",
95+
avatarUrl: "mxc://example/avatar",
96+
},
97+
}),
98+
);
99+
100+
expect(mocks.handleMatrixAction).toHaveBeenCalledWith(
101+
expect.objectContaining({
102+
action: "setProfile",
103+
accountId: "ops",
104+
displayName: "Ops Bot",
105+
avatarUrl: "mxc://example/avatar",
106+
}),
107+
expect.any(Object),
108+
{ mediaLocalRoots: undefined },
109+
);
110+
});
111+
112+
it("forwards local avatar paths for self-profile updates", async () => {
113+
await matrixMessageActions.handleAction?.(
114+
createContext({
115+
action: "set-profile",
116+
accountId: "ops",
117+
params: {
118+
path: "/tmp/avatar.jpg",
119+
},
120+
}),
121+
);
122+
123+
expect(mocks.handleMatrixAction).toHaveBeenCalledWith(
124+
expect.objectContaining({
125+
action: "setProfile",
126+
accountId: "ops",
127+
avatarPath: "/tmp/avatar.jpg",
128+
}),
129+
expect.any(Object),
130+
{ mediaLocalRoots: undefined },
131+
);
132+
});
133+
134+
it("forwards mediaLocalRoots for media sends", async () => {
135+
await matrixMessageActions.handleAction?.(
136+
createContext({
137+
action: "send",
138+
accountId: "ops",
139+
mediaLocalRoots: ["/tmp/openclaw-matrix-test"],
140+
params: {
141+
to: "room:!room:example",
142+
message: "hello",
143+
media: "file:///tmp/photo.png",
144+
},
145+
}),
146+
);
147+
148+
expect(mocks.handleMatrixAction).toHaveBeenCalledWith(
149+
expect.objectContaining({
150+
action: "sendMessage",
151+
accountId: "ops",
152+
mediaUrl: "file:///tmp/photo.png",
153+
}),
154+
expect.any(Object),
155+
{ mediaLocalRoots: ["/tmp/openclaw-matrix-test"] },
156+
);
157+
});
158+
159+
it("allows media-only sends without requiring a message body", async () => {
160+
await matrixMessageActions.handleAction?.(
161+
createContext({
162+
action: "send",
163+
accountId: "ops",
164+
params: {
165+
to: "room:!room:example",
166+
media: "file:///tmp/photo.png",
167+
},
168+
}),
169+
);
170+
171+
expect(mocks.handleMatrixAction).toHaveBeenCalledWith(
172+
expect.objectContaining({
173+
action: "sendMessage",
174+
accountId: "ops",
175+
content: undefined,
176+
mediaUrl: "file:///tmp/photo.png",
177+
}),
178+
expect.any(Object),
179+
{ mediaLocalRoots: undefined },
180+
);
181+
});
182+
});

0 commit comments

Comments
 (0)