Matrix: normalize legacy account selection

This commit is contained in:
Gustavo Madeira Santana
2026-03-09 12:04:12 -04:00
parent ede2500137
commit 450ed87cb0
3 changed files with 92 additions and 18 deletions

View File

@@ -0,0 +1,60 @@
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import {
findMatrixAccountEntry,
resolveConfiguredMatrixAccountIds,
resolveMatrixDefaultOrOnlyAccountId,
} from "./matrix-account-selection.js";
describe("matrix account selection", () => {
it("resolves configured account ids from non-canonical account keys", () => {
const cfg: OpenClawConfig = {
channels: {
matrix: {
accounts: {
"Team Ops": { homeserver: "https://matrix.example.org" },
},
},
},
};
expect(resolveConfiguredMatrixAccountIds(cfg)).toEqual(["team-ops"]);
expect(resolveMatrixDefaultOrOnlyAccountId(cfg)).toBe("team-ops");
});
it("matches the default account against normalized Matrix account keys", () => {
const cfg: OpenClawConfig = {
channels: {
matrix: {
defaultAccount: "Team Ops",
accounts: {
"Ops Bot": { homeserver: "https://matrix.example.org" },
"Team Ops": { homeserver: "https://matrix.example.org" },
},
},
},
};
expect(resolveMatrixDefaultOrOnlyAccountId(cfg)).toBe("team-ops");
});
it("finds the raw Matrix account entry by normalized account id", () => {
const cfg: OpenClawConfig = {
channels: {
matrix: {
accounts: {
"Team Ops": {
homeserver: "https://matrix.example.org",
userId: "@ops:example.org",
},
},
},
},
};
expect(findMatrixAccountEntry(cfg, "team-ops")).toEqual({
homeserver: "https://matrix.example.org",
userId: "@ops:example.org",
});
});
});

View File

@@ -13,6 +13,30 @@ export function resolveMatrixChannelConfig(cfg: OpenClawConfig): Record<string,
return isRecord(cfg.channels?.matrix) ? cfg.channels.matrix : null; return isRecord(cfg.channels?.matrix) ? cfg.channels.matrix : null;
} }
export function findMatrixAccountEntry(
cfg: OpenClawConfig,
accountId: string,
): Record<string, unknown> | null {
const channel = resolveMatrixChannelConfig(cfg);
if (!channel) {
return null;
}
const accounts = isRecord(channel.accounts) ? channel.accounts : null;
if (!accounts) {
return null;
}
const normalizedAccountId = normalizeAccountId(accountId);
for (const [rawAccountId, value] of Object.entries(accounts)) {
if (normalizeAccountId(rawAccountId) === normalizedAccountId && isRecord(value)) {
return value;
}
}
return null;
}
export function resolveConfiguredMatrixAccountIds(cfg: OpenClawConfig): string[] { export function resolveConfiguredMatrixAccountIds(cfg: OpenClawConfig): string[] {
const channel = resolveMatrixChannelConfig(cfg); const channel = resolveMatrixChannelConfig(cfg);
if (!channel) { if (!channel) {
@@ -24,9 +48,9 @@ export function resolveConfiguredMatrixAccountIds(cfg: OpenClawConfig): string[]
return [DEFAULT_ACCOUNT_ID]; return [DEFAULT_ACCOUNT_ID];
} }
const ids = Object.keys(accounts) const ids = Object.entries(accounts)
.map((accountId) => normalizeAccountId(accountId)) .filter(([, value]) => isRecord(value))
.filter((accountId) => accountId.length > 0 && isRecord(accounts[accountId])); .map(([accountId]) => normalizeAccountId(accountId));
return Array.from(new Set(ids.length > 0 ? ids : [DEFAULT_ACCOUNT_ID])).toSorted((a, b) => return Array.from(new Set(ids.length > 0 ? ids : [DEFAULT_ACCOUNT_ID])).toSorted((a, b) =>
a.localeCompare(b), a.localeCompare(b),
@@ -39,18 +63,17 @@ export function resolveMatrixDefaultOrOnlyAccountId(cfg: OpenClawConfig): string
return DEFAULT_ACCOUNT_ID; return DEFAULT_ACCOUNT_ID;
} }
const accounts = isRecord(channel.accounts) ? channel.accounts : null;
const configuredDefault = normalizeOptionalAccountId( const configuredDefault = normalizeOptionalAccountId(
typeof channel.defaultAccount === "string" ? channel.defaultAccount : undefined, typeof channel.defaultAccount === "string" ? channel.defaultAccount : undefined,
); );
if (configuredDefault && accounts && isRecord(accounts[configuredDefault])) { const configuredAccountIds = resolveConfiguredMatrixAccountIds(cfg);
if (configuredDefault && configuredAccountIds.includes(configuredDefault)) {
return configuredDefault; return configuredDefault;
} }
if (accounts && isRecord(accounts[DEFAULT_ACCOUNT_ID])) { if (configuredAccountIds.includes(DEFAULT_ACCOUNT_ID)) {
return DEFAULT_ACCOUNT_ID; return DEFAULT_ACCOUNT_ID;
} }
const configuredAccountIds = resolveConfiguredMatrixAccountIds(cfg);
if (configuredAccountIds.length === 1) { if (configuredAccountIds.length === 1) {
return configuredAccountIds[0] ?? DEFAULT_ACCOUNT_ID; return configuredAccountIds[0] ?? DEFAULT_ACCOUNT_ID;
} }

View File

@@ -3,7 +3,7 @@ import os from "node:os";
import type { OpenClawConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js"; import { resolveStateDir } from "../config/paths.js";
import { normalizeAccountId } from "../routing/session-key.js"; import { normalizeAccountId } from "../routing/session-key.js";
import { resolveMatrixChannelConfig } from "./matrix-account-selection.js"; import { findMatrixAccountEntry, resolveMatrixChannelConfig } from "./matrix-account-selection.js";
import { resolveMatrixCredentialsPath } from "./matrix-storage-paths.js"; import { resolveMatrixCredentialsPath } from "./matrix-storage-paths.js";
export type MatrixStoredCredentials = { export type MatrixStoredCredentials = {
@@ -13,10 +13,6 @@ export type MatrixStoredCredentials = {
deviceId?: string; deviceId?: string;
}; };
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function clean(value: unknown): string { function clean(value: unknown): string {
return typeof value === "string" ? value.trim() : ""; return typeof value === "string" ? value.trim() : "";
} }
@@ -60,12 +56,7 @@ function resolveMatrixAccountConfigEntry(
cfg: OpenClawConfig, cfg: OpenClawConfig,
accountId: string, accountId: string,
): Record<string, unknown> | null { ): Record<string, unknown> | null {
const channel = resolveMatrixChannelConfig(cfg); return findMatrixAccountEntry(cfg, accountId);
if (!channel) {
return null;
}
const accounts = isRecord(channel.accounts) ? channel.accounts : null;
return accounts && isRecord(accounts[accountId]) ? accounts[accountId] : null;
} }
export function resolveMatrixMigrationConfigFields(params: { export function resolveMatrixMigrationConfigFields(params: {