diff --git a/CHANGELOG.md b/CHANGELOG.md index f40342fcf..26023ebc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702) - **BREAKING:** Microsoft Teams is now a plugin; install `@clawdbot/msteams` via `clawdbot plugins install @clawdbot/msteams`. - **BREAKING:** Discord/Telegram channel tokens now prefer config over env (env is fallback only). +- **BREAKING:** Matrix channel credentials now prefer config over env (env is fallback only). ### Changes - CLI: set process titles to `clawdbot-` for clearer process listings. diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md index add7d4930..22ffbaf2f 100644 --- a/docs/channels/matrix.md +++ b/docs/channels/matrix.md @@ -32,6 +32,7 @@ Details: [Plugins](/plugin) 2) Configure credentials: - Env: `MATRIX_HOMESERVER`, `MATRIX_USER_ID`, `MATRIX_ACCESS_TOKEN` (or `MATRIX_PASSWORD`) - Or config: `channels.matrix.*` + - Config takes precedence over env; env is fallback. 3) Restart the gateway (or finish onboarding). 4) DM access defaults to pairing; approve the pairing code on first contact. diff --git a/extensions/matrix/src/matrix/client.test.ts b/extensions/matrix/src/matrix/client.test.ts new file mode 100644 index 000000000..547d0d981 --- /dev/null +++ b/extensions/matrix/src/matrix/client.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "vitest"; + +import type { CoreConfig } from "../types.js"; +import { resolveMatrixConfig } from "./client.js"; + +describe("resolveMatrixConfig", () => { + it("prefers config over env", () => { + const cfg = { + channels: { + matrix: { + homeserver: "https://cfg.example.org", + userId: "@cfg:example.org", + accessToken: "cfg-token", + password: "cfg-pass", + deviceName: "CfgDevice", + initialSyncLimit: 5, + }, + }, + } as CoreConfig; + const env = { + MATRIX_HOMESERVER: "https://env.example.org", + MATRIX_USER_ID: "@env:example.org", + MATRIX_ACCESS_TOKEN: "env-token", + MATRIX_PASSWORD: "env-pass", + MATRIX_DEVICE_NAME: "EnvDevice", + } as NodeJS.ProcessEnv; + const resolved = resolveMatrixConfig(cfg, env); + expect(resolved).toEqual({ + homeserver: "https://cfg.example.org", + userId: "@cfg:example.org", + accessToken: "cfg-token", + password: "cfg-pass", + deviceName: "CfgDevice", + initialSyncLimit: 5, + }); + }); + + it("uses env when config is missing", () => { + const cfg = {} as CoreConfig; + const env = { + MATRIX_HOMESERVER: "https://env.example.org", + MATRIX_USER_ID: "@env:example.org", + MATRIX_ACCESS_TOKEN: "env-token", + MATRIX_PASSWORD: "env-pass", + MATRIX_DEVICE_NAME: "EnvDevice", + } as NodeJS.ProcessEnv; + const resolved = resolveMatrixConfig(cfg, env); + expect(resolved.homeserver).toBe("https://env.example.org"); + expect(resolved.userId).toBe("@env:example.org"); + expect(resolved.accessToken).toBe("env-token"); + expect(resolved.password).toBe("env-pass"); + expect(resolved.deviceName).toBe("EnvDevice"); + expect(resolved.initialSyncLimit).toBeUndefined(); + }); +}); diff --git a/extensions/matrix/src/matrix/client.ts b/extensions/matrix/src/matrix/client.ts index 51f1c4631..a2b4be35a 100644 --- a/extensions/matrix/src/matrix/client.ts +++ b/extensions/matrix/src/matrix/client.ts @@ -50,13 +50,13 @@ export function resolveMatrixConfig( env: NodeJS.ProcessEnv = process.env, ): MatrixResolvedConfig { const matrix = cfg.channels?.matrix ?? {}; - const homeserver = clean(env.MATRIX_HOMESERVER) || clean(matrix.homeserver); - const userId = clean(env.MATRIX_USER_ID) || clean(matrix.userId); + const homeserver = clean(matrix.homeserver) || clean(env.MATRIX_HOMESERVER); + const userId = clean(matrix.userId) || clean(env.MATRIX_USER_ID); const accessToken = - clean(env.MATRIX_ACCESS_TOKEN) || clean(matrix.accessToken) || undefined; - const password = clean(env.MATRIX_PASSWORD) || clean(matrix.password) || undefined; + clean(matrix.accessToken) || clean(env.MATRIX_ACCESS_TOKEN) || undefined; + const password = clean(matrix.password) || clean(env.MATRIX_PASSWORD) || undefined; const deviceName = - clean(env.MATRIX_DEVICE_NAME) || clean(matrix.deviceName) || undefined; + clean(matrix.deviceName) || clean(env.MATRIX_DEVICE_NAME) || undefined; const initialSyncLimit = typeof matrix.initialSyncLimit === "number" ? Math.max(0, Math.floor(matrix.initialSyncLimit))