Matrix: share config field path helpers

This commit is contained in:
Gustavo Madeira Santana
2026-03-09 05:17:00 -04:00
parent 26da307dc0
commit d6b12a3835
5 changed files with 179 additions and 100 deletions

View File

@@ -11,6 +11,7 @@ import {
listNormalizedMatrixAccountIds,
resolveMatrixBaseConfig,
} from "../account-config.js";
import { resolveMatrixConfigFieldPath } from "../config-update.js";
import { MatrixClient } from "../sdk.js";
import { ensureMatrixSdkLoggingConfigured } from "./logging.js";
import type { MatrixAuth, MatrixResolvedConfig } from "./types.js";
@@ -28,6 +29,54 @@ type MatrixEnvConfig = {
deviceName?: string;
};
type MatrixConfigStringField =
| "homeserver"
| "userId"
| "accessToken"
| "password"
| "deviceId"
| "deviceName";
function resolveMatrixBaseConfigFieldPath(field: MatrixConfigStringField): string {
return `channels.matrix.${field}`;
}
function readMatrixBaseConfigField(
matrix: ReturnType<typeof resolveMatrixBaseConfig>,
field: MatrixConfigStringField,
): string {
return clean(matrix[field], resolveMatrixBaseConfigFieldPath(field));
}
function readMatrixAccountConfigField(
cfg: CoreConfig,
accountId: string,
account: Partial<Record<MatrixConfigStringField, unknown>>,
field: MatrixConfigStringField,
): string {
return clean(account[field], resolveMatrixConfigFieldPath(cfg, accountId, field));
}
function resolveMatrixStringField(params: {
matrix: ReturnType<typeof resolveMatrixBaseConfig>;
field: MatrixConfigStringField;
accountValue?: string;
scopedEnvValue?: string;
globalEnvValue?: string;
}): string {
return (
params.accountValue ||
params.scopedEnvValue ||
readMatrixBaseConfigField(params.matrix, params.field) ||
params.globalEnvValue ||
""
);
}
function clampMatrixInitialSyncLimit(value: unknown): number | undefined {
return typeof value === "number" ? Math.max(0, Math.floor(value)) : undefined;
}
function resolveGlobalMatrixEnvConfig(env: NodeJS.ProcessEnv): MatrixEnvConfig {
return {
homeserver: clean(env.MATRIX_HOMESERVER, "MATRIX_HOMESERVER"),
@@ -100,36 +149,47 @@ export function resolveMatrixConfig(
const matrix = resolveMatrixBaseConfig(cfg);
const defaultScopedEnv = resolveScopedMatrixEnvConfig(DEFAULT_ACCOUNT_ID, env);
const globalEnv = resolveGlobalMatrixEnvConfig(env);
const homeserver =
clean(matrix.homeserver, "channels.matrix.homeserver") ||
defaultScopedEnv.homeserver ||
globalEnv.homeserver;
const userId =
clean(matrix.userId, "channels.matrix.userId") || defaultScopedEnv.userId || globalEnv.userId;
const homeserver = resolveMatrixStringField({
matrix,
field: "homeserver",
scopedEnvValue: defaultScopedEnv.homeserver,
globalEnvValue: globalEnv.homeserver,
});
const userId = resolveMatrixStringField({
matrix,
field: "userId",
scopedEnvValue: defaultScopedEnv.userId,
globalEnvValue: globalEnv.userId,
});
const accessToken =
clean(matrix.accessToken, "channels.matrix.accessToken") ||
defaultScopedEnv.accessToken ||
globalEnv.accessToken ||
undefined;
resolveMatrixStringField({
matrix,
field: "accessToken",
scopedEnvValue: defaultScopedEnv.accessToken,
globalEnvValue: globalEnv.accessToken,
}) || undefined;
const password =
clean(matrix.password, "channels.matrix.password") ||
defaultScopedEnv.password ||
globalEnv.password ||
undefined;
resolveMatrixStringField({
matrix,
field: "password",
scopedEnvValue: defaultScopedEnv.password,
globalEnvValue: globalEnv.password,
}) || undefined;
const deviceId =
clean(matrix.deviceId, "channels.matrix.deviceId") ||
defaultScopedEnv.deviceId ||
globalEnv.deviceId ||
undefined;
resolveMatrixStringField({
matrix,
field: "deviceId",
scopedEnvValue: defaultScopedEnv.deviceId,
globalEnvValue: globalEnv.deviceId,
}) || undefined;
const deviceName =
clean(matrix.deviceName, "channels.matrix.deviceName") ||
defaultScopedEnv.deviceName ||
globalEnv.deviceName ||
undefined;
const initialSyncLimit =
typeof matrix.initialSyncLimit === "number"
? Math.max(0, Math.floor(matrix.initialSyncLimit))
: undefined;
resolveMatrixStringField({
matrix,
field: "deviceName",
scopedEnvValue: defaultScopedEnv.deviceName,
globalEnvValue: globalEnv.deviceName,
}) || undefined;
const initialSyncLimit = clampMatrixInitialSyncLimit(matrix.initialSyncLimit);
const encryption = matrix.encryption ?? false;
return {
homeserver,
@@ -153,76 +213,58 @@ export function resolveMatrixConfigForAccount(
const normalizedAccountId = normalizeAccountId(accountId);
const scopedEnv = resolveScopedMatrixEnvConfig(normalizedAccountId, env);
const globalEnv = resolveGlobalMatrixEnvConfig(env);
const accountHomeserver = clean(
account.homeserver,
`channels.matrix.accounts.${normalizedAccountId}.homeserver`,
);
const accountUserId = clean(
account.userId,
`channels.matrix.accounts.${normalizedAccountId}.userId`,
);
const accountAccessToken = clean(
account.accessToken,
`channels.matrix.accounts.${normalizedAccountId}.accessToken`,
);
const accountPassword = clean(
account.password,
`channels.matrix.accounts.${normalizedAccountId}.password`,
);
const accountDeviceId = clean(
account.deviceId,
`channels.matrix.accounts.${normalizedAccountId}.deviceId`,
);
const accountDeviceName = clean(
account.deviceName,
`channels.matrix.accounts.${normalizedAccountId}.deviceName`,
);
const homeserver =
accountHomeserver ||
scopedEnv.homeserver ||
clean(matrix.homeserver, "channels.matrix.homeserver") ||
globalEnv.homeserver;
const userId =
accountUserId ||
scopedEnv.userId ||
clean(matrix.userId, "channels.matrix.userId") ||
globalEnv.userId;
const accountField = (field: MatrixConfigStringField) =>
readMatrixAccountConfigField(cfg, normalizedAccountId, account, field);
const homeserver = resolveMatrixStringField({
matrix,
field: "homeserver",
accountValue: accountField("homeserver"),
scopedEnvValue: scopedEnv.homeserver,
globalEnvValue: globalEnv.homeserver,
});
const userId = resolveMatrixStringField({
matrix,
field: "userId",
accountValue: accountField("userId"),
scopedEnvValue: scopedEnv.userId,
globalEnvValue: globalEnv.userId,
});
const accessToken =
accountAccessToken ||
scopedEnv.accessToken ||
clean(matrix.accessToken, "channels.matrix.accessToken") ||
globalEnv.accessToken ||
undefined;
resolveMatrixStringField({
matrix,
field: "accessToken",
accountValue: accountField("accessToken"),
scopedEnvValue: scopedEnv.accessToken,
globalEnvValue: globalEnv.accessToken,
}) || undefined;
const password =
accountPassword ||
scopedEnv.password ||
clean(matrix.password, "channels.matrix.password") ||
globalEnv.password ||
undefined;
resolveMatrixStringField({
matrix,
field: "password",
accountValue: accountField("password"),
scopedEnvValue: scopedEnv.password,
globalEnvValue: globalEnv.password,
}) || undefined;
const deviceId =
accountDeviceId ||
scopedEnv.deviceId ||
clean(matrix.deviceId, "channels.matrix.deviceId") ||
globalEnv.deviceId ||
undefined;
resolveMatrixStringField({
matrix,
field: "deviceId",
accountValue: accountField("deviceId"),
scopedEnvValue: scopedEnv.deviceId,
globalEnvValue: globalEnv.deviceId,
}) || undefined;
const deviceName =
accountDeviceName ||
scopedEnv.deviceName ||
clean(matrix.deviceName, "channels.matrix.deviceName") ||
globalEnv.deviceName ||
undefined;
resolveMatrixStringField({
matrix,
field: "deviceName",
accountValue: accountField("deviceName"),
scopedEnvValue: scopedEnv.deviceName,
globalEnvValue: globalEnv.deviceName,
}) || undefined;
const accountInitialSyncLimit =
typeof account.initialSyncLimit === "number"
? Math.max(0, Math.floor(account.initialSyncLimit))
: undefined;
const accountInitialSyncLimit = clampMatrixInitialSyncLimit(account.initialSyncLimit);
const initialSyncLimit =
accountInitialSyncLimit ??
(typeof matrix.initialSyncLimit === "number"
? Math.max(0, Math.floor(matrix.initialSyncLimit))
: undefined);
accountInitialSyncLimit ?? clampMatrixInitialSyncLimit(matrix.initialSyncLimit);
const encryption =
typeof account.encryption === "boolean" ? account.encryption : (matrix.encryption ?? false);

View File

@@ -1,8 +1,28 @@
import { describe, expect, it } from "vitest";
import type { CoreConfig } from "../types.js";
import { updateMatrixAccountConfig } from "./config-update.js";
import { resolveMatrixConfigFieldPath, updateMatrixAccountConfig } from "./config-update.js";
describe("updateMatrixAccountConfig", () => {
it("resolves account-aware Matrix config field paths", () => {
expect(resolveMatrixConfigFieldPath({} as CoreConfig, "default", "dm.policy")).toBe(
"channels.matrix.dm.policy",
);
const cfg = {
channels: {
matrix: {
accounts: {
ops: {},
},
},
},
} as CoreConfig;
expect(resolveMatrixConfigFieldPath(cfg, "ops", ".dm.allowFrom")).toBe(
"channels.matrix.accounts.ops.dm.allowFrom",
);
});
it("supports explicit null clears and boolean false values", () => {
const cfg = {
channels: {

View File

@@ -93,6 +93,18 @@ export function resolveMatrixConfigPath(cfg: CoreConfig, accountId: string): str
return `channels.matrix.accounts.${normalizedAccountId}`;
}
export function resolveMatrixConfigFieldPath(
cfg: CoreConfig,
accountId: string,
fieldPath: string,
): string {
const suffix = fieldPath.trim().replace(/^\.+/, "");
if (!suffix) {
return resolveMatrixConfigPath(cfg, accountId);
}
return `${resolveMatrixConfigPath(cfg, accountId)}.${suffix}`;
}
export function updateMatrixAccountConfig(
cfg: CoreConfig,
accountId: string,

View File

@@ -1,7 +1,7 @@
import { normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id";
import type { CoreConfig } from "../types.js";
import { resolveDefaultMatrixAccountId } from "./accounts.js";
import { resolveMatrixConfigPath } from "./config-update.js";
import { resolveMatrixConfigFieldPath } from "./config-update.js";
export function resolveMatrixEncryptionConfigPath(
cfg: CoreConfig,
@@ -9,7 +9,7 @@ export function resolveMatrixEncryptionConfigPath(
): string {
const effectiveAccountId =
normalizeOptionalAccountId(accountId) ?? resolveDefaultMatrixAccountId(cfg);
return `${resolveMatrixConfigPath(cfg, effectiveAccountId)}.encryption`;
return resolveMatrixConfigFieldPath(cfg, effectiveAccountId, "encryption");
}
export function formatMatrixEncryptionUnavailableError(

View File

@@ -24,7 +24,11 @@ import {
hasReadyMatrixEnvAuth,
resolveScopedMatrixEnvConfig,
} from "./matrix/client.js";
import { resolveMatrixConfigPath, updateMatrixAccountConfig } from "./matrix/config-update.js";
import {
resolveMatrixConfigFieldPath,
resolveMatrixConfigPath,
updateMatrixAccountConfig,
} from "./matrix/config-update.js";
import { ensureMatrixSdkInstalled, isMatrixSdkAvailable } from "./matrix/deps.js";
import { resolveMatrixTargets } from "./resolve-targets.js";
import type { CoreConfig } from "./types.js";
@@ -176,13 +180,14 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
policyKey: "channels.matrix.dm.policy",
allowFromKey: "channels.matrix.dm.allowFrom",
resolveConfigKeys: (cfg, accountId) => {
const basePath = resolveMatrixConfigPath(
cfg as CoreConfig,
resolveMatrixOnboardingAccountId(cfg as CoreConfig, accountId),
);
const effectiveAccountId = resolveMatrixOnboardingAccountId(cfg as CoreConfig, accountId);
return {
policyKey: `${basePath}.dm.policy`,
allowFromKey: `${basePath}.dm.allowFrom`,
policyKey: resolveMatrixConfigFieldPath(cfg as CoreConfig, effectiveAccountId, "dm.policy"),
allowFromKey: resolveMatrixConfigFieldPath(
cfg as CoreConfig,
effectiveAccountId,
"dm.allowFrom",
),
};
},
getCurrent: (cfg, accountId) =>