From 3911be9795056fe8ec0b20840248e1aa8d4efa05 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 25 Feb 2026 20:54:20 -0500 Subject: [PATCH] Matrix-js: migrate account-scoped defaults into accounts.default --- .../matrix-js/src/channel.directory.test.ts | 42 +++++++++- extensions/matrix-js/src/config-migration.ts | 81 ++++++++++++++++--- 2 files changed, 113 insertions(+), 10 deletions(-) diff --git a/extensions/matrix-js/src/channel.directory.test.ts b/extensions/matrix-js/src/channel.directory.test.ts index da907503cd6..80d0fc62f11 100644 --- a/extensions/matrix-js/src/channel.directory.test.ts +++ b/extensions/matrix-js/src/channel.directory.test.ts @@ -200,11 +200,17 @@ describe("matrix directory", () => { const cfg = { channels: { "matrix-js": { + name: "pinguini", homeserver: "https://legacy.example.org", userId: "@legacy:example.org", accessToken: "legacy-token", deviceName: "Legacy Device", encryption: true, + groupPolicy: "allowlist", + groups: { + "!legacy-room:example.org": { allow: true }, + }, + register: false, }, }, } as unknown as CoreConfig; @@ -213,12 +219,46 @@ describe("matrix directory", () => { expect(updated.channels?.["matrix-js"]?.homeserver).toBeUndefined(); expect(updated.channels?.["matrix-js"]?.accessToken).toBeUndefined(); expect(updated.channels?.["matrix-js"]?.deviceName).toBeUndefined(); - expect(updated.channels?.["matrix-js"]?.encryption).toBe(true); + expect(updated.channels?.["matrix-js"]?.encryption).toBeUndefined(); + expect((updated.channels?.["matrix-js"] as Record)?.register).toBeUndefined(); expect(updated.channels?.["matrix-js"]?.accounts?.default).toMatchObject({ + name: "pinguini", homeserver: "https://legacy.example.org", userId: "@legacy:example.org", accessToken: "legacy-token", deviceName: "Legacy Device", + encryption: true, + groupPolicy: "allowlist", + groups: { + "!legacy-room:example.org": { allow: true }, + }, + }); + }); + + it("merges top-level object defaults into accounts.default during migration", () => { + const cfg = { + channels: { + "matrix-js": { + dm: { + policy: "allowlist", + allowFrom: ["@legacy:example.org"], + }, + accounts: { + default: { + dm: { + policy: "pairing", + }, + }, + }, + }, + }, + } as unknown as CoreConfig; + + const updated = migrateMatrixLegacyCredentialsToDefaultAccount(cfg); + expect(updated.channels?.["matrix-js"]?.dm).toBeUndefined(); + expect(updated.channels?.["matrix-js"]?.accounts?.default?.dm).toMatchObject({ + policy: "pairing", + allowFrom: ["@legacy:example.org"], }); }); diff --git a/extensions/matrix-js/src/config-migration.ts b/extensions/matrix-js/src/config-migration.ts index 30663a2f8d3..3af3eb349a3 100644 --- a/extensions/matrix-js/src/config-migration.ts +++ b/extensions/matrix-js/src/config-migration.ts @@ -2,15 +2,33 @@ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk"; import type { CoreConfig, MatrixAccountConfig, MatrixConfig } from "./types.js"; type LegacyAccountField = + | "name" | "homeserver" | "userId" | "accessToken" | "password" | "deviceId" | "deviceName" - | "initialSyncLimit"; + | "initialSyncLimit" + | "encryption" + | "allowlistOnly" + | "groupPolicy" + | "groupAllowFrom" + | "replyToMode" + | "threadReplies" + | "textChunkLimit" + | "chunkMode" + | "responsePrefix" + | "mediaMaxMb" + | "autoJoin" + | "autoJoinAllowlist" + | "dm" + | "groups" + | "rooms" + | "actions"; const LEGACY_ACCOUNT_FIELDS: ReadonlyArray = [ + "name", "homeserver", "userId", "accessToken", @@ -18,8 +36,49 @@ const LEGACY_ACCOUNT_FIELDS: ReadonlyArray = [ "deviceId", "deviceName", "initialSyncLimit", + "encryption", + "allowlistOnly", + "groupPolicy", + "groupAllowFrom", + "replyToMode", + "threadReplies", + "textChunkLimit", + "chunkMode", + "responsePrefix", + "mediaMaxMb", + "autoJoin", + "autoJoinAllowlist", + "dm", + "groups", + "rooms", + "actions", ]; +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function mergeLegacyFieldIntoDefault( + current: MatrixAccountConfig[LegacyAccountField] | undefined, + legacy: MatrixAccountConfig[LegacyAccountField], +): MatrixAccountConfig[LegacyAccountField] { + if (current === undefined) { + return legacy; + } + if (isRecord(current) && isRecord(legacy)) { + return { + ...legacy, + ...current, + } as MatrixAccountConfig[LegacyAccountField]; + } + return current; +} + +function clearLegacyOnlyFields(nextMatrix: MatrixConfig): void { + // Legacy matrix-bot-sdk onboarding toggle; not used by matrix-js config. + delete (nextMatrix as Record).register; +} + export function migrateMatrixLegacyCredentialsToDefaultAccount(cfg: CoreConfig): CoreConfig { const matrix = cfg.channels?.["matrix-js"]; if (!matrix) { @@ -36,14 +95,17 @@ export function migrateMatrixLegacyCredentialsToDefaultAccount(cfg: CoreConfig): if (legacyValue === undefined) { continue; } - if (defaultAccount[field] === undefined) { - ( - defaultAccount as Record< - LegacyAccountField, - MatrixAccountConfig[LegacyAccountField] | undefined - > - )[field] = legacyValue; - } + ( + defaultAccount as Record< + LegacyAccountField, + MatrixAccountConfig[LegacyAccountField] | undefined + > + )[field] = mergeLegacyFieldIntoDefault(defaultAccount[field], legacyValue); + changed = true; + } + + const registerPresent = (matrix as Record).register !== undefined; + if (registerPresent) { changed = true; } @@ -55,6 +117,7 @@ export function migrateMatrixLegacyCredentialsToDefaultAccount(cfg: CoreConfig): for (const field of LEGACY_ACCOUNT_FIELDS) { delete nextMatrix[field]; } + clearLegacyOnlyFields(nextMatrix); nextMatrix.accounts = { ...matrix.accounts, [DEFAULT_ACCOUNT_ID]: defaultAccount,