Matrix: trim test import breadth

This commit is contained in:
Gustavo Madeira Santana 2026-03-30 19:54:40 -04:00
parent fa2e051bb6
commit bd957a3a8b
No known key found for this signature in database
4 changed files with 202 additions and 143 deletions

View File

@ -28,33 +28,6 @@ vi.mock("./credentials-write.runtime.js", () => ({
touchMatrixCredentials: touchMatrixCredentialsMock,
}));
let getMatrixScopedEnvVarNames: typeof import("./client/config.js").getMatrixScopedEnvVarNames;
let resolveImplicitMatrixAccountId: typeof import("./client/config.js").resolveImplicitMatrixAccountId;
let resolveMatrixConfig: typeof import("./client/config.js").resolveMatrixConfig;
let resolveMatrixConfigForAccount: typeof import("./client/config.js").resolveMatrixConfigForAccount;
let resolveMatrixAuth: typeof import("./client/config.js").resolveMatrixAuth;
let resolveMatrixAuthContext: typeof import("./client/config.js").resolveMatrixAuthContext;
let resolveValidatedMatrixHomeserverUrl: typeof import("./client/config.js").resolveValidatedMatrixHomeserverUrl;
let validateMatrixHomeserverUrl: typeof import("./client/config.js").validateMatrixHomeserverUrl;
let credentialsReadModule: typeof import("./credentials-read.js");
let sdkModule: typeof import("./sdk.js");
beforeEach(async () => {
vi.resetModules();
({
getMatrixScopedEnvVarNames,
resolveImplicitMatrixAccountId,
resolveMatrixConfig,
resolveMatrixConfigForAccount,
resolveMatrixAuth,
resolveMatrixAuthContext,
resolveValidatedMatrixHomeserverUrl,
validateMatrixHomeserverUrl,
} = await import("./client/config.js"));
credentialsReadModule = await import("./credentials-read.js");
sdkModule = await import("./sdk.js");
});
vi.mock("matrix-js-sdk", async (importOriginal) => {
const actual = await importOriginal<typeof import("matrix-js-sdk")>();
return {
@ -66,6 +39,24 @@ vi.mock("matrix-js-sdk", async (importOriginal) => {
};
});
const {
getMatrixScopedEnvVarNames,
resolveImplicitMatrixAccountId,
resolveMatrixConfig,
resolveMatrixConfigForAccount,
resolveMatrixAuth,
resolveMatrixAuthContext,
resolveValidatedMatrixHomeserverUrl,
validateMatrixHomeserverUrl,
} = await import("./client/config.js");
let credentialsReadModule: typeof import("./credentials-read.js") | undefined;
let sdkModule: typeof import("./sdk.js") | undefined;
beforeEach(() => {
installMatrixTestRuntime();
});
describe("resolveMatrixConfig", () => {
it("prefers config over env", () => {
const cfg = {
@ -600,18 +591,30 @@ describe("resolveMatrixConfig", () => {
});
describe("resolveMatrixAuth", () => {
beforeEach(async () => {
credentialsReadModule ??= await import("./credentials-read.js");
sdkModule ??= await import("./sdk.js");
vi.mocked(credentialsReadModule.loadMatrixCredentials).mockReset();
vi.mocked(credentialsReadModule.loadMatrixCredentials).mockReturnValue(null);
vi.mocked(credentialsReadModule.credentialsMatchConfig).mockReset();
vi.mocked(credentialsReadModule.credentialsMatchConfig).mockReturnValue(false);
saveMatrixCredentialsMock.mockReset();
touchMatrixCredentialsMock.mockReset();
});
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
saveMatrixCredentialsMock.mockReset();
});
it("uses the hardened client request path for password login and persists deviceId", async () => {
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
access_token: "tok-123",
user_id: "@bot:example.org",
device_id: "DEVICE123",
});
const doRequestSpy = vi
.spyOn(sdkModule!.MatrixClient.prototype, "doRequest")
.mockResolvedValue({
access_token: "tok-123",
user_id: "@bot:example.org",
device_id: "DEVICE123",
});
const cfg = {
channels: {
@ -658,7 +661,7 @@ describe("resolveMatrixAuth", () => {
});
it("surfaces password login errors when account credentials are invalid", async () => {
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest");
const doRequestSpy = vi.spyOn(sdkModule!.MatrixClient.prototype, "doRequest");
doRequestSpy.mockRejectedValueOnce(new Error("Invalid username or password"));
const cfg = {
@ -690,14 +693,14 @@ describe("resolveMatrixAuth", () => {
});
it("uses cached matching credentials when access token is not configured", async () => {
vi.mocked(credentialsReadModule.loadMatrixCredentials).mockReturnValue({
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue({
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "cached-token",
deviceId: "CACHEDDEVICE",
createdAt: "2026-01-01T00:00:00.000Z",
});
vi.mocked(credentialsReadModule.credentialsMatchConfig).mockReturnValue(true);
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(true);
const cfg = {
channels: {
@ -740,13 +743,13 @@ describe("resolveMatrixAuth", () => {
});
it("falls back to config deviceId when cached credentials are missing it", async () => {
vi.mocked(credentialsReadModule.loadMatrixCredentials).mockReturnValue({
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue({
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok-123",
createdAt: "2026-01-01T00:00:00.000Z",
});
vi.mocked(credentialsReadModule.credentialsMatchConfig).mockReturnValue(true);
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(true);
const cfg = {
channels: {
@ -799,10 +802,12 @@ describe("resolveMatrixAuth", () => {
});
it("resolves token-only non-default account userId from whoami instead of inheriting the base user", async () => {
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
user_id: "@ops:example.org",
device_id: "OPSDEVICE",
});
const doRequestSpy = vi
.spyOn(sdkModule!.MatrixClient.prototype, "doRequest")
.mockResolvedValue({
user_id: "@ops:example.org",
device_id: "OPSDEVICE",
});
const cfg = {
channels: {
@ -831,13 +836,15 @@ describe("resolveMatrixAuth", () => {
});
it("uses named-account password auth instead of inheriting the base access token", async () => {
vi.mocked(credentialsReadModule.loadMatrixCredentials).mockReturnValue(null);
vi.mocked(credentialsReadModule.credentialsMatchConfig).mockReturnValue(false);
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
access_token: "ops-token",
user_id: "@ops:example.org",
device_id: "OPSDEVICE",
});
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue(null);
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(false);
const doRequestSpy = vi
.spyOn(sdkModule!.MatrixClient.prototype, "doRequest")
.mockResolvedValue({
access_token: "ops-token",
user_id: "@ops:example.org",
device_id: "OPSDEVICE",
});
const cfg = {
channels: {
@ -881,10 +888,12 @@ describe("resolveMatrixAuth", () => {
});
it("resolves missing whoami identity fields for token auth", async () => {
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
user_id: "@bot:example.org",
device_id: "DEVICE123",
});
const doRequestSpy = vi
.spyOn(sdkModule!.MatrixClient.prototype, "doRequest")
.mockResolvedValue({
user_id: "@bot:example.org",
device_id: "DEVICE123",
});
const cfg = {
channels: {
@ -918,10 +927,12 @@ describe("resolveMatrixAuth", () => {
await fs.writeFile(secretPath, "file-token\n", "utf8");
await fs.chmod(secretPath, 0o600);
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
user_id: "@bot:example.org",
device_id: "DEVICE123",
});
const doRequestSpy = vi
.spyOn(sdkModule!.MatrixClient.prototype, "doRequest")
.mockResolvedValue({
user_id: "@bot:example.org",
device_id: "DEVICE123",
});
try {
const cfg = {
@ -961,10 +972,12 @@ describe("resolveMatrixAuth", () => {
});
it("does not resolve inactive password SecretRefs when scoped token auth wins", async () => {
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
user_id: "@ops:example.org",
device_id: "OPSDEVICE",
});
const doRequestSpy = vi
.spyOn(sdkModule!.MatrixClient.prototype, "doRequest")
.mockResolvedValue({
user_id: "@ops:example.org",
device_id: "OPSDEVICE",
});
const cfg = {
channels: {
@ -1006,13 +1019,13 @@ describe("resolveMatrixAuth", () => {
});
it("uses config deviceId with cached credentials when token is loaded from cache", async () => {
vi.mocked(credentialsReadModule.loadMatrixCredentials).mockReturnValue({
vi.mocked(credentialsReadModule!.loadMatrixCredentials).mockReturnValue({
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok-123",
createdAt: "2026-01-01T00:00:00.000Z",
});
vi.mocked(credentialsReadModule.credentialsMatchConfig).mockReturnValue(true);
vi.mocked(credentialsReadModule!.credentialsMatchConfig).mockReturnValue(true);
const cfg = {
channels: {

View File

@ -28,10 +28,25 @@ import {
} from "../account-config.js";
import { resolveMatrixConfigFieldPath } from "../config-update.js";
import { credentialsMatchConfig, loadMatrixCredentials } from "../credentials-read.js";
import { MatrixClient } from "../sdk.js";
import { ensureMatrixSdkLoggingConfigured } from "./logging.js";
import type { MatrixAuth, MatrixResolvedConfig } from "./types.js";
type MatrixAuthClientDeps = {
MatrixClient: typeof import("../sdk.js").MatrixClient;
ensureMatrixSdkLoggingConfigured: typeof import("./logging.js").ensureMatrixSdkLoggingConfigured;
};
let matrixAuthClientDepsPromise: Promise<MatrixAuthClientDeps> | undefined;
async function loadMatrixAuthClientDeps(): Promise<MatrixAuthClientDeps> {
matrixAuthClientDepsPromise ??= Promise.all([import("../sdk.js"), import("./logging.js")]).then(
([sdkModule, loggingModule]) => ({
MatrixClient: sdkModule.MatrixClient,
ensureMatrixSdkLoggingConfigured: loggingModule.ensureMatrixSdkLoggingConfigured,
}),
);
return await matrixAuthClientDepsPromise;
}
function readEnvSecretRefFallback(params: {
value: unknown;
env?: NodeJS.ProcessEnv;
@ -670,6 +685,7 @@ export async function resolveMatrixAuth(params?: {
if (!userId || !knownDeviceId) {
// Fetch whoami when we need to resolve userId and/or deviceId from token auth.
const { MatrixClient, ensureMatrixSdkLoggingConfigured } = await loadMatrixAuthClientDeps();
ensureMatrixSdkLoggingConfigured();
const tempClient = new MatrixClient(homeserver, accessToken, {
ssrfPolicy: resolved.ssrfPolicy,
@ -767,6 +783,7 @@ export async function resolveMatrixAuth(params?: {
}
// Login with password using the same hardened request path as other Matrix HTTP calls.
const { MatrixClient, ensureMatrixSdkLoggingConfigured } = await loadMatrixAuthClientDeps();
ensureMatrixSdkLoggingConfigured();
const loginClient = new MatrixClient(homeserver, "", {
ssrfPolicy: resolved.ssrfPolicy,

View File

@ -1,8 +1,12 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { CoreConfig } from "../../types.js";
import type { MatrixAccountPatch } from "../config-update.js";
import type { MatrixManagedDeviceInfo } from "../device-health.js";
import type { MatrixProfileSyncResult } from "../profile.js";
import type { MatrixOwnDeviceVerificationStatus } from "../sdk.js";
import type { MatrixLegacyCryptoRestoreResult } from "./legacy-crypto-restore.js";
import type { MatrixStartupVerificationOutcome } from "./startup-verification.js";
import type { MatrixStartupMaintenanceDeps } from "./startup.js";
import { runMatrixStartupMaintenance } from "./startup.js";
function createVerificationStatus(
@ -67,47 +71,32 @@ function createLegacyCryptoRestoreResult(
} as MatrixLegacyCryptoRestoreResult;
}
const hoisted = vi.hoisted(() => ({
maybeRestoreLegacyMatrixBackup: vi.fn(async () => createLegacyCryptoRestoreResult()),
summarizeMatrixDeviceHealth: vi.fn(() => ({
staleOpenClawDevices: [] as Array<{ deviceId: string }>,
})),
syncMatrixOwnProfile: vi.fn(async () => createProfileSyncResult()),
ensureMatrixStartupVerification: vi.fn(async () => createStartupVerificationOutcome("verified")),
updateMatrixAccountConfig: vi.fn((cfg: unknown) => cfg),
}));
vi.mock("../config-update.js", () => ({
updateMatrixAccountConfig: hoisted.updateMatrixAccountConfig,
}));
vi.mock("../device-health.js", () => ({
summarizeMatrixDeviceHealth: hoisted.summarizeMatrixDeviceHealth,
}));
vi.mock("../profile.js", () => ({
syncMatrixOwnProfile: hoisted.syncMatrixOwnProfile,
}));
vi.mock("./legacy-crypto-restore.js", () => ({
maybeRestoreLegacyMatrixBackup: hoisted.maybeRestoreLegacyMatrixBackup,
}));
vi.mock("./startup-verification.js", () => ({
ensureMatrixStartupVerification: hoisted.ensureMatrixStartupVerification,
}));
function createDeps(
overrides: Partial<MatrixStartupMaintenanceDeps> = {},
): MatrixStartupMaintenanceDeps {
return {
maybeRestoreLegacyMatrixBackup: vi.fn(async () => createLegacyCryptoRestoreResult()),
summarizeMatrixDeviceHealth: vi.fn(() => ({
currentDeviceId: null,
staleOpenClawDevices: [] as MatrixManagedDeviceInfo[],
currentOpenClawDevices: [] as MatrixManagedDeviceInfo[],
})),
syncMatrixOwnProfile: vi.fn(async () => createProfileSyncResult()),
ensureMatrixStartupVerification: vi.fn(async () =>
createStartupVerificationOutcome("verified"),
),
updateMatrixAccountConfig: vi.fn(
(cfg: CoreConfig, _accountId: string, _patch: MatrixAccountPatch) => cfg,
),
...overrides,
};
}
describe("runMatrixStartupMaintenance", () => {
let deps: MatrixStartupMaintenanceDeps;
beforeEach(() => {
hoisted.maybeRestoreLegacyMatrixBackup
.mockClear()
.mockResolvedValue(createLegacyCryptoRestoreResult());
hoisted.summarizeMatrixDeviceHealth.mockClear().mockReturnValue({ staleOpenClawDevices: [] });
hoisted.syncMatrixOwnProfile.mockClear().mockResolvedValue(createProfileSyncResult());
hoisted.ensureMatrixStartupVerification
.mockClear()
.mockResolvedValue(createStartupVerificationOutcome("verified"));
hoisted.updateMatrixAccountConfig.mockClear().mockImplementation((cfg: unknown) => cfg);
deps = createDeps();
});
function createParams(): Parameters<typeof runMatrixStartupMaintenance>[0] {
@ -151,7 +140,7 @@ describe("runMatrixStartupMaintenance", () => {
it("persists converted avatar URLs after profile sync", async () => {
const params = createParams();
const updatedCfg = { channels: { matrix: { avatarUrl: "mxc://avatar" } } };
hoisted.syncMatrixOwnProfile.mockResolvedValue(
vi.mocked(deps.syncMatrixOwnProfile).mockResolvedValue(
createProfileSyncResult({
avatarUpdated: true,
resolvedAvatarUrl: "mxc://avatar",
@ -159,18 +148,18 @@ describe("runMatrixStartupMaintenance", () => {
convertedAvatarFromHttp: true,
}),
);
hoisted.updateMatrixAccountConfig.mockReturnValue(updatedCfg);
vi.mocked(deps.updateMatrixAccountConfig).mockReturnValue(updatedCfg);
await runMatrixStartupMaintenance(params);
await runMatrixStartupMaintenance(params, deps);
expect(hoisted.syncMatrixOwnProfile).toHaveBeenCalledWith(
expect(deps.syncMatrixOwnProfile).toHaveBeenCalledWith(
expect.objectContaining({
userId: "@bot:example.org",
displayName: "Ops Bot",
avatarUrl: "https://example.org/avatar.png",
}),
);
expect(hoisted.updateMatrixAccountConfig).toHaveBeenCalledWith(
expect(deps.updateMatrixAccountConfig).toHaveBeenCalledWith(
{ channels: { matrix: {} } },
"ops",
{ avatarUrl: "mxc://avatar" },
@ -184,13 +173,17 @@ describe("runMatrixStartupMaintenance", () => {
it("reports stale devices, pending verification, and restored legacy backups", async () => {
const params = createParams();
params.auth.encryption = true;
hoisted.summarizeMatrixDeviceHealth.mockReturnValue({
staleOpenClawDevices: [{ deviceId: "DEV123" }],
vi.mocked(deps.summarizeMatrixDeviceHealth).mockReturnValue({
currentDeviceId: null,
staleOpenClawDevices: [
{ deviceId: "DEV123", displayName: "OpenClaw Device", current: false },
],
currentOpenClawDevices: [],
});
hoisted.ensureMatrixStartupVerification.mockResolvedValue(
vi.mocked(deps.ensureMatrixStartupVerification).mockResolvedValue(
createStartupVerificationOutcome("pending"),
);
hoisted.maybeRestoreLegacyMatrixBackup.mockResolvedValue(
vi.mocked(deps.maybeRestoreLegacyMatrixBackup).mockResolvedValue(
createLegacyCryptoRestoreResult({
kind: "restored",
imported: 2,
@ -199,7 +192,7 @@ describe("runMatrixStartupMaintenance", () => {
}),
);
await runMatrixStartupMaintenance(params);
await runMatrixStartupMaintenance(params, deps);
expect(params.logger.warn).toHaveBeenCalledWith(
"matrix: stale OpenClaw devices detected for @bot:example.org: DEV123. Run 'openclaw matrix devices prune-stale --account ops' to keep encrypted-room trust healthy.",
@ -221,21 +214,21 @@ describe("runMatrixStartupMaintenance", () => {
it("logs cooldown and request-failure verification outcomes without throwing", async () => {
const params = createParams();
params.auth.encryption = true;
hoisted.ensureMatrixStartupVerification.mockResolvedValueOnce(
vi.mocked(deps.ensureMatrixStartupVerification).mockResolvedValueOnce(
createStartupVerificationOutcome("cooldown", { retryAfterMs: 321 }),
);
await runMatrixStartupMaintenance(params);
await runMatrixStartupMaintenance(params, deps);
expect(params.logVerboseMessage).toHaveBeenCalledWith(
"matrix: skipped startup verification request due to cooldown (retryAfterMs=321)",
);
hoisted.ensureMatrixStartupVerification.mockResolvedValueOnce(
vi.mocked(deps.ensureMatrixStartupVerification).mockResolvedValueOnce(
createStartupVerificationOutcome("request-failed", { error: "boom" }),
);
await runMatrixStartupMaintenance(params);
await runMatrixStartupMaintenance(params, deps);
expect(params.logger.debug).toHaveBeenCalledWith(
"Matrix startup verification request failed (non-fatal)",

View File

@ -1,12 +1,7 @@
import type { RuntimeLogger } from "../../runtime-api.js";
import type { CoreConfig, MatrixConfig } from "../../types.js";
import type { MatrixAuth } from "../client.js";
import { updateMatrixAccountConfig } from "../config-update.js";
import { summarizeMatrixDeviceHealth } from "../device-health.js";
import { syncMatrixOwnProfile } from "../profile.js";
import type { MatrixClient } from "../sdk.js";
import { maybeRestoreLegacyMatrixBackup } from "./legacy-crypto-restore.js";
import { ensureMatrixStartupVerification } from "./startup-verification.js";
type MatrixStartupClient = Pick<
MatrixClient,
@ -20,24 +15,63 @@ type MatrixStartupClient = Pick<
| "uploadContent"
>;
export async function runMatrixStartupMaintenance(params: {
client: MatrixStartupClient;
auth: MatrixAuth;
accountId: string;
effectiveAccountId: string;
accountConfig: MatrixConfig;
logger: RuntimeLogger;
logVerboseMessage: (message: string) => void;
loadConfig: () => CoreConfig;
writeConfigFile: (cfg: never) => Promise<void>;
loadWebMedia: (
url: string,
maxBytes: number,
) => Promise<{ buffer: Buffer; contentType?: string; fileName?: string }>;
env?: NodeJS.ProcessEnv;
}): Promise<void> {
export type MatrixStartupMaintenanceDeps = {
updateMatrixAccountConfig: typeof import("../config-update.js").updateMatrixAccountConfig;
summarizeMatrixDeviceHealth: typeof import("../device-health.js").summarizeMatrixDeviceHealth;
syncMatrixOwnProfile: typeof import("../profile.js").syncMatrixOwnProfile;
maybeRestoreLegacyMatrixBackup: typeof import("./legacy-crypto-restore.js").maybeRestoreLegacyMatrixBackup;
ensureMatrixStartupVerification: typeof import("./startup-verification.js").ensureMatrixStartupVerification;
};
let matrixStartupMaintenanceDepsPromise: Promise<MatrixStartupMaintenanceDeps> | undefined;
async function loadMatrixStartupMaintenanceDeps(): Promise<MatrixStartupMaintenanceDeps> {
matrixStartupMaintenanceDepsPromise ??= Promise.all([
import("../config-update.js"),
import("../device-health.js"),
import("../profile.js"),
import("./legacy-crypto-restore.js"),
import("./startup-verification.js"),
]).then(
([
configUpdateModule,
deviceHealthModule,
profileModule,
legacyCryptoRestoreModule,
startupVerificationModule,
]) => ({
updateMatrixAccountConfig: configUpdateModule.updateMatrixAccountConfig,
summarizeMatrixDeviceHealth: deviceHealthModule.summarizeMatrixDeviceHealth,
syncMatrixOwnProfile: profileModule.syncMatrixOwnProfile,
maybeRestoreLegacyMatrixBackup: legacyCryptoRestoreModule.maybeRestoreLegacyMatrixBackup,
ensureMatrixStartupVerification: startupVerificationModule.ensureMatrixStartupVerification,
}),
);
return await matrixStartupMaintenanceDepsPromise;
}
export async function runMatrixStartupMaintenance(
params: {
client: MatrixStartupClient;
auth: MatrixAuth;
accountId: string;
effectiveAccountId: string;
accountConfig: MatrixConfig;
logger: RuntimeLogger;
logVerboseMessage: (message: string) => void;
loadConfig: () => CoreConfig;
writeConfigFile: (cfg: never) => Promise<void>;
loadWebMedia: (
url: string,
maxBytes: number,
) => Promise<{ buffer: Buffer; contentType?: string; fileName?: string }>;
env?: NodeJS.ProcessEnv;
},
deps?: MatrixStartupMaintenanceDeps,
): Promise<void> {
const runtimeDeps = deps ?? (await loadMatrixStartupMaintenanceDeps());
try {
const profileSync = await syncMatrixOwnProfile({
const profileSync = await runtimeDeps.syncMatrixOwnProfile({
client: params.client,
userId: params.auth.userId,
displayName: params.accountConfig.name,
@ -56,7 +90,7 @@ export async function runMatrixStartupMaintenance(params: {
params.accountConfig.avatarUrl !== profileSync.resolvedAvatarUrl
) {
const latestCfg = params.loadConfig();
const updatedCfg = updateMatrixAccountConfig(latestCfg, params.accountId, {
const updatedCfg = runtimeDeps.updateMatrixAccountConfig(latestCfg, params.accountId, {
avatarUrl: profileSync.resolvedAvatarUrl,
});
await params.writeConfigFile(updatedCfg as never);
@ -73,7 +107,9 @@ export async function runMatrixStartupMaintenance(params: {
}
try {
const deviceHealth = summarizeMatrixDeviceHealth(await params.client.listOwnDevices());
const deviceHealth = runtimeDeps.summarizeMatrixDeviceHealth(
await params.client.listOwnDevices(),
);
if (deviceHealth.staleOpenClawDevices.length > 0) {
params.logger.warn(
`matrix: stale OpenClaw devices detected for ${params.auth.userId}: ${deviceHealth.staleOpenClawDevices.map((device) => device.deviceId).join(", ")}. Run 'openclaw matrix devices prune-stale --account ${params.effectiveAccountId}' to keep encrypted-room trust healthy.`,
@ -86,7 +122,7 @@ export async function runMatrixStartupMaintenance(params: {
}
try {
const startupVerification = await ensureMatrixStartupVerification({
const startupVerification = await runtimeDeps.ensureMatrixStartupVerification({
client: params.client,
auth: params.auth,
accountConfig: params.accountConfig,
@ -128,7 +164,7 @@ export async function runMatrixStartupMaintenance(params: {
}
try {
const legacyCryptoRestore = await maybeRestoreLegacyMatrixBackup({
const legacyCryptoRestore = await runtimeDeps.maybeRestoreLegacyMatrixBackup({
client: params.client,
auth: params.auth,
env: params.env,