mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fMatrix: fix remaining typecheck regressions
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
import { vi } from "vitest";
|
||||
import { vi, type Mock } from "vitest";
|
||||
import type { MatrixClient } from "./sdk.js";
|
||||
|
||||
export const matrixClientResolverMocks = {
|
||||
type MatrixClientResolverMocks = {
|
||||
loadConfigMock: Mock<() => unknown>;
|
||||
getMatrixRuntimeMock: Mock<() => unknown>;
|
||||
getActiveMatrixClientMock: Mock<(...args: unknown[]) => MatrixClient | null>;
|
||||
createMatrixClientMock: Mock<(...args: unknown[]) => Promise<MatrixClient>>;
|
||||
isBunRuntimeMock: Mock<() => boolean>;
|
||||
resolveMatrixAuthMock: Mock<(...args: unknown[]) => Promise<unknown>>;
|
||||
resolveMatrixAuthContextMock: Mock<
|
||||
(params: { cfg: unknown; accountId?: string | null }) => unknown
|
||||
>;
|
||||
};
|
||||
|
||||
export const matrixClientResolverMocks: MatrixClientResolverMocks = {
|
||||
loadConfigMock: vi.fn(() => ({})),
|
||||
getMatrixRuntimeMock: vi.fn(),
|
||||
getActiveMatrixClientMock: vi.fn(),
|
||||
|
||||
@@ -102,7 +102,9 @@ export function createDirectRoomTracker(client: MatrixClient, opts: DirectRoomTr
|
||||
const memberCount = await resolveMemberCount(roomId);
|
||||
if (memberCount === 2) {
|
||||
try {
|
||||
const nameState = await client.getRoomStateEvent(roomId, "m.room.name", "");
|
||||
const nameState = (await client.getRoomStateEvent(roomId, "m.room.name", "")) as {
|
||||
name?: string | null;
|
||||
} | null;
|
||||
if (!nameState?.name?.trim()) {
|
||||
log(`matrix: dm detected via fallback (2 members, no room name) room=${roomId}`);
|
||||
return true;
|
||||
|
||||
@@ -64,7 +64,23 @@ type MatrixHandlerTestHarnessOptions = {
|
||||
getMemberDisplayName?: MatrixMonitorHandlerParams["getMemberDisplayName"];
|
||||
};
|
||||
|
||||
export function createMatrixHandlerTestHarness(options: MatrixHandlerTestHarnessOptions = {}) {
|
||||
type MatrixHandlerTestHarness = {
|
||||
dispatchReplyFromConfig: () => Promise<{
|
||||
queuedFinal: boolean;
|
||||
counts: { final: number; block: number; tool: number };
|
||||
}>;
|
||||
enqueueSystemEvent: (...args: unknown[]) => void;
|
||||
finalizeInboundContext: (ctx: unknown) => unknown;
|
||||
handler: ReturnType<typeof createMatrixRoomMessageHandler>;
|
||||
readAllowFromStore: MatrixMonitorHandlerParams["core"]["channel"]["pairing"]["readAllowFromStore"];
|
||||
recordInboundSession: (...args: unknown[]) => Promise<void>;
|
||||
resolveAgentRoute: () => typeof DEFAULT_ROUTE;
|
||||
upsertPairingRequest: MatrixMonitorHandlerParams["core"]["channel"]["pairing"]["upsertPairingRequest"];
|
||||
};
|
||||
|
||||
export function createMatrixHandlerTestHarness(
|
||||
options: MatrixHandlerTestHarnessOptions = {},
|
||||
): MatrixHandlerTestHarness {
|
||||
const readAllowFromStore = options.readAllowFromStore ?? vi.fn(async () => [] as string[]);
|
||||
const upsertPairingRequest =
|
||||
options.upsertPairingRequest ?? vi.fn(async () => ({ code: "ABCDEFGH", created: false }));
|
||||
@@ -109,7 +125,8 @@ export function createMatrixHandlerTestHarness(options: MatrixHandlerTestHarness
|
||||
},
|
||||
reply: {
|
||||
resolveEnvelopeFormatOptions: options.resolveEnvelopeFormatOptions ?? (() => ({})),
|
||||
formatAgentEnvelope: options.formatAgentEnvelope ?? (({ body }) => body),
|
||||
formatAgentEnvelope:
|
||||
options.formatAgentEnvelope ?? (({ body }: { body: string }) => body),
|
||||
finalizeInboundContext,
|
||||
createReplyDispatcherWithTyping:
|
||||
options.createReplyDispatcherWithTyping ??
|
||||
|
||||
@@ -188,7 +188,7 @@ describe("matrix monitor handler pairing account scope", () => {
|
||||
accountId: "ops",
|
||||
sessionKey: "agent:ops:main",
|
||||
mainSessionKey: "agent:ops:main",
|
||||
matchedBy: "binding.account",
|
||||
matchedBy: "binding.account" as const,
|
||||
}));
|
||||
|
||||
const { handler } = createMatrixHandlerTestHarness({
|
||||
|
||||
@@ -1,66 +1,121 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
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 { runMatrixStartupMaintenance } from "./startup.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
maybeRestoreLegacyMatrixBackup: vi.fn(async () => ({ kind: "skipped" as const })),
|
||||
summarizeMatrixDeviceHealth: vi.fn(() => ({
|
||||
staleOpenClawDevices: [] as Array<{ deviceId: string }>,
|
||||
})),
|
||||
syncMatrixOwnProfile: vi.fn(async () => ({
|
||||
function createVerificationStatus(
|
||||
overrides: Partial<MatrixOwnDeviceVerificationStatus> = {},
|
||||
): MatrixOwnDeviceVerificationStatus {
|
||||
return {
|
||||
encryptionEnabled: true,
|
||||
userId: "@bot:example.org",
|
||||
deviceId: "DEVICE",
|
||||
verified: false,
|
||||
localVerified: false,
|
||||
crossSigningVerified: false,
|
||||
signedByOwner: false,
|
||||
recoveryKeyStored: false,
|
||||
recoveryKeyCreatedAt: null,
|
||||
recoveryKeyId: null,
|
||||
backupVersion: null,
|
||||
backup: {
|
||||
serverVersion: null,
|
||||
activeVersion: null,
|
||||
trusted: null,
|
||||
matchesDecryptionKey: null,
|
||||
decryptionKeyCached: null,
|
||||
keyLoadAttempted: false,
|
||||
keyLoadError: null,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createProfileSyncResult(
|
||||
overrides: Partial<MatrixProfileSyncResult> = {},
|
||||
): MatrixProfileSyncResult {
|
||||
return {
|
||||
skipped: false,
|
||||
displayNameUpdated: false,
|
||||
avatarUpdated: false,
|
||||
resolvedAvatarUrl: null,
|
||||
uploadedAvatarSource: null,
|
||||
convertedAvatarFromHttp: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createStartupVerificationOutcome(
|
||||
kind: Exclude<MatrixStartupVerificationOutcome["kind"], "unsupported">,
|
||||
overrides: Partial<Extract<MatrixStartupVerificationOutcome, { kind: typeof kind }>> = {},
|
||||
): MatrixStartupVerificationOutcome {
|
||||
return {
|
||||
kind,
|
||||
verification: createVerificationStatus({ verified: kind === "verified" }),
|
||||
...overrides,
|
||||
} as MatrixStartupVerificationOutcome;
|
||||
}
|
||||
|
||||
function createLegacyCryptoRestoreResult(
|
||||
overrides: Partial<MatrixLegacyCryptoRestoreResult> = {},
|
||||
): MatrixLegacyCryptoRestoreResult {
|
||||
return {
|
||||
kind: "skipped",
|
||||
...overrides,
|
||||
} as MatrixLegacyCryptoRestoreResult;
|
||||
}
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
maybeRestoreLegacyMatrixBackup: vi.fn(async () => createLegacyCryptoRestoreResult()),
|
||||
summarizeMatrixDeviceHealth: vi.fn(() => ({
|
||||
staleOpenClawDevices: [] as Array<{ deviceId: string }>,
|
||||
})),
|
||||
ensureMatrixStartupVerification: vi.fn(async () => ({ kind: "verified" as const })),
|
||||
syncMatrixOwnProfile: vi.fn(async () => createProfileSyncResult()),
|
||||
ensureMatrixStartupVerification: vi.fn(async () => createStartupVerificationOutcome("verified")),
|
||||
updateMatrixAccountConfig: vi.fn((cfg: unknown) => cfg),
|
||||
}));
|
||||
|
||||
vi.mock("../config-update.js", () => ({
|
||||
updateMatrixAccountConfig: (...args: unknown[]) => hoisted.updateMatrixAccountConfig(...args),
|
||||
updateMatrixAccountConfig: hoisted.updateMatrixAccountConfig,
|
||||
}));
|
||||
|
||||
vi.mock("../device-health.js", () => ({
|
||||
summarizeMatrixDeviceHealth: (...args: unknown[]) => hoisted.summarizeMatrixDeviceHealth(...args),
|
||||
summarizeMatrixDeviceHealth: hoisted.summarizeMatrixDeviceHealth,
|
||||
}));
|
||||
|
||||
vi.mock("../profile.js", () => ({
|
||||
syncMatrixOwnProfile: (...args: unknown[]) => hoisted.syncMatrixOwnProfile(...args),
|
||||
syncMatrixOwnProfile: hoisted.syncMatrixOwnProfile,
|
||||
}));
|
||||
|
||||
vi.mock("./legacy-crypto-restore.js", () => ({
|
||||
maybeRestoreLegacyMatrixBackup: (...args: unknown[]) =>
|
||||
hoisted.maybeRestoreLegacyMatrixBackup(...args),
|
||||
maybeRestoreLegacyMatrixBackup: hoisted.maybeRestoreLegacyMatrixBackup,
|
||||
}));
|
||||
|
||||
vi.mock("./startup-verification.js", () => ({
|
||||
ensureMatrixStartupVerification: (...args: unknown[]) =>
|
||||
hoisted.ensureMatrixStartupVerification(...args),
|
||||
ensureMatrixStartupVerification: hoisted.ensureMatrixStartupVerification,
|
||||
}));
|
||||
|
||||
describe("runMatrixStartupMaintenance", () => {
|
||||
beforeEach(() => {
|
||||
hoisted.maybeRestoreLegacyMatrixBackup.mockClear().mockResolvedValue({ kind: "skipped" });
|
||||
hoisted.maybeRestoreLegacyMatrixBackup
|
||||
.mockClear()
|
||||
.mockResolvedValue(createLegacyCryptoRestoreResult());
|
||||
hoisted.summarizeMatrixDeviceHealth.mockClear().mockReturnValue({ staleOpenClawDevices: [] });
|
||||
hoisted.syncMatrixOwnProfile.mockClear().mockResolvedValue({
|
||||
skipped: false,
|
||||
displayNameUpdated: false,
|
||||
avatarUpdated: false,
|
||||
resolvedAvatarUrl: null,
|
||||
uploadedAvatarSource: null,
|
||||
convertedAvatarFromHttp: false,
|
||||
});
|
||||
hoisted.ensureMatrixStartupVerification.mockClear().mockResolvedValue({ kind: "verified" });
|
||||
hoisted.syncMatrixOwnProfile.mockClear().mockResolvedValue(createProfileSyncResult());
|
||||
hoisted.ensureMatrixStartupVerification
|
||||
.mockClear()
|
||||
.mockResolvedValue(createStartupVerificationOutcome("verified"));
|
||||
hoisted.updateMatrixAccountConfig.mockClear().mockImplementation((cfg: unknown) => cfg);
|
||||
});
|
||||
|
||||
function createParams() {
|
||||
function createParams(): Parameters<typeof runMatrixStartupMaintenance>[0] {
|
||||
return {
|
||||
client: {
|
||||
crypto: {},
|
||||
listOwnDevices: vi.fn(async () => []),
|
||||
getOwnDeviceVerificationStatus: vi.fn(async () => createVerificationStatus()),
|
||||
} as never,
|
||||
auth: {
|
||||
accountId: "ops",
|
||||
@@ -90,20 +145,20 @@ describe("runMatrixStartupMaintenance", () => {
|
||||
fileName: "avatar.png",
|
||||
})),
|
||||
env: {},
|
||||
} as const;
|
||||
};
|
||||
}
|
||||
|
||||
it("persists converted avatar URLs after profile sync", async () => {
|
||||
const params = createParams();
|
||||
const updatedCfg = { channels: { matrix: { avatarUrl: "mxc://avatar" } } };
|
||||
hoisted.syncMatrixOwnProfile.mockResolvedValue({
|
||||
skipped: false,
|
||||
displayNameUpdated: false,
|
||||
avatarUpdated: true,
|
||||
resolvedAvatarUrl: "mxc://avatar",
|
||||
uploadedAvatarSource: "http",
|
||||
convertedAvatarFromHttp: true,
|
||||
});
|
||||
hoisted.syncMatrixOwnProfile.mockResolvedValue(
|
||||
createProfileSyncResult({
|
||||
avatarUpdated: true,
|
||||
resolvedAvatarUrl: "mxc://avatar",
|
||||
uploadedAvatarSource: "http",
|
||||
convertedAvatarFromHttp: true,
|
||||
}),
|
||||
);
|
||||
hoisted.updateMatrixAccountConfig.mockReturnValue(updatedCfg);
|
||||
|
||||
await runMatrixStartupMaintenance(params);
|
||||
@@ -132,13 +187,17 @@ describe("runMatrixStartupMaintenance", () => {
|
||||
hoisted.summarizeMatrixDeviceHealth.mockReturnValue({
|
||||
staleOpenClawDevices: [{ deviceId: "DEV123" }],
|
||||
});
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValue({ kind: "pending" });
|
||||
hoisted.maybeRestoreLegacyMatrixBackup.mockResolvedValue({
|
||||
kind: "restored",
|
||||
imported: 2,
|
||||
total: 3,
|
||||
localOnlyKeys: 1,
|
||||
});
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValue(
|
||||
createStartupVerificationOutcome("pending"),
|
||||
);
|
||||
hoisted.maybeRestoreLegacyMatrixBackup.mockResolvedValue(
|
||||
createLegacyCryptoRestoreResult({
|
||||
kind: "restored",
|
||||
imported: 2,
|
||||
total: 3,
|
||||
localOnlyKeys: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
await runMatrixStartupMaintenance(params);
|
||||
|
||||
@@ -162,10 +221,9 @@ describe("runMatrixStartupMaintenance", () => {
|
||||
it("logs cooldown and request-failure verification outcomes without throwing", async () => {
|
||||
const params = createParams();
|
||||
params.auth.encryption = true;
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValueOnce({
|
||||
kind: "cooldown",
|
||||
retryAfterMs: 321,
|
||||
});
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValueOnce(
|
||||
createStartupVerificationOutcome("cooldown", { retryAfterMs: 321 }),
|
||||
);
|
||||
|
||||
await runMatrixStartupMaintenance(params);
|
||||
|
||||
@@ -173,10 +231,9 @@ describe("runMatrixStartupMaintenance", () => {
|
||||
"matrix: skipped startup verification request due to cooldown (retryAfterMs=321)",
|
||||
);
|
||||
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValueOnce({
|
||||
kind: "request-failed",
|
||||
error: "boom",
|
||||
});
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValueOnce(
|
||||
createStartupVerificationOutcome("request-failed", { error: "boom" }),
|
||||
);
|
||||
|
||||
await runMatrixStartupMaintenance(params);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ensureMatrixStartupVerification } from "./startup-verification.js";
|
||||
type MatrixStartupClient = Pick<
|
||||
MatrixClient,
|
||||
| "crypto"
|
||||
| "getOwnDeviceVerificationStatus"
|
||||
| "getUserProfile"
|
||||
| "listOwnDevices"
|
||||
| "restoreRoomKeyBackup"
|
||||
|
||||
@@ -801,9 +801,9 @@ export class MatrixClient {
|
||||
}
|
||||
|
||||
let defaultKeyId: string | null | undefined = undefined;
|
||||
const canReadSecretStorageStatus = typeof crypto.getSecretStorageStatus === "function"; // pragma: allowlist secret
|
||||
if (canReadSecretStorageStatus) {
|
||||
const status = await crypto.getSecretStorageStatus().catch(() => null); // pragma: allowlist secret
|
||||
const getSecretStorageStatus = crypto.getSecretStorageStatus; // pragma: allowlist secret
|
||||
if (typeof getSecretStorageStatus === "function") {
|
||||
const status = await getSecretStorageStatus.call(crypto).catch(() => null); // pragma: allowlist secret
|
||||
defaultKeyId = status?.defaultKeyId;
|
||||
}
|
||||
|
||||
@@ -870,9 +870,9 @@ export class MatrixClient {
|
||||
const rawRecoveryKey = params.recoveryKey?.trim();
|
||||
if (rawRecoveryKey) {
|
||||
let defaultKeyId: string | null | undefined = undefined;
|
||||
const canReadSecretStorageStatus = typeof crypto.getSecretStorageStatus === "function"; // pragma: allowlist secret
|
||||
if (canReadSecretStorageStatus) {
|
||||
const status = await crypto.getSecretStorageStatus().catch(() => null); // pragma: allowlist secret
|
||||
const getSecretStorageStatus = crypto.getSecretStorageStatus; // pragma: allowlist secret
|
||||
if (typeof getSecretStorageStatus === "function") {
|
||||
const status = await getSecretStorageStatus.call(crypto).catch(() => null); // pragma: allowlist secret
|
||||
defaultKeyId = status?.defaultKeyId;
|
||||
}
|
||||
this.recoveryKeyStore.storeEncodedRecoveryKey({
|
||||
@@ -1077,9 +1077,9 @@ export class MatrixClient {
|
||||
const rawRecoveryKey = params?.recoveryKey?.trim();
|
||||
if (rawRecoveryKey) {
|
||||
let defaultKeyId: string | null | undefined = undefined;
|
||||
const canReadSecretStorageStatus = typeof crypto.getSecretStorageStatus === "function"; // pragma: allowlist secret
|
||||
if (canReadSecretStorageStatus) {
|
||||
const status = await crypto.getSecretStorageStatus().catch(() => null); // pragma: allowlist secret
|
||||
const getSecretStorageStatus = crypto.getSecretStorageStatus; // pragma: allowlist secret
|
||||
if (typeof getSecretStorageStatus === "function") {
|
||||
const status = await getSecretStorageStatus.call(crypto).catch(() => null); // pragma: allowlist secret
|
||||
defaultKeyId = status?.defaultKeyId;
|
||||
}
|
||||
this.recoveryKeyStore.storeEncodedRecoveryKey({
|
||||
@@ -1190,11 +1190,11 @@ export class MatrixClient {
|
||||
private async resolveCachedRoomKeyBackupDecryptionKey(
|
||||
crypto: MatrixCryptoBootstrapApi,
|
||||
): Promise<boolean | null> {
|
||||
const canGetSessionBackupPrivateKey = typeof crypto.getSessionBackupPrivateKey === "function"; // pragma: allowlist secret
|
||||
if (!canGetSessionBackupPrivateKey) {
|
||||
const getSessionBackupPrivateKey = crypto.getSessionBackupPrivateKey; // pragma: allowlist secret
|
||||
if (typeof getSessionBackupPrivateKey !== "function") {
|
||||
return null;
|
||||
}
|
||||
const key = await crypto.getSessionBackupPrivateKey().catch(() => null); // pragma: allowlist secret
|
||||
const key = await getSessionBackupPrivateKey.call(crypto).catch(() => null); // pragma: allowlist secret
|
||||
return key ? key.length > 0 : false;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,10 +152,10 @@ export class MatrixRecoveryKeyStore {
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
let status: MatrixSecretStorageStatus | null = null;
|
||||
const canReadSecretStorageStatus = typeof crypto.getSecretStorageStatus === "function"; // pragma: allowlist secret
|
||||
if (canReadSecretStorageStatus) {
|
||||
const getSecretStorageStatus = crypto.getSecretStorageStatus; // pragma: allowlist secret
|
||||
if (typeof getSecretStorageStatus === "function") {
|
||||
try {
|
||||
status = await crypto.getSecretStorageStatus();
|
||||
status = await getSecretStorageStatus.call(crypto);
|
||||
} catch (err) {
|
||||
LogService.warn("MatrixClientLite", "Failed to read secret storage status:", err);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ vi.mock("../client.js", () => ({
|
||||
createMatrixClient: (...args: unknown[]) => createMatrixClientMock(...args),
|
||||
isBunRuntime: () => isBunRuntimeMock(),
|
||||
resolveMatrixAuth: (...args: unknown[]) => resolveMatrixAuthMock(...args),
|
||||
resolveMatrixAuthContext: (...args: unknown[]) => resolveMatrixAuthContextMock(...args),
|
||||
resolveMatrixAuthContext: resolveMatrixAuthContextMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../runtime.js", () => ({
|
||||
|
||||
@@ -294,4 +294,37 @@ describe("matrix onboarding", () => {
|
||||
allowFromKey: "channels.matrix.accounts.ops.dm.allowFrom",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports configured when only the effective default Matrix account is configured", async () => {
|
||||
setMatrixRuntime({
|
||||
state: {
|
||||
resolveStateDir: (_env: NodeJS.ProcessEnv, homeDir?: () => string) =>
|
||||
(homeDir ?? (() => "/tmp"))(),
|
||||
},
|
||||
config: {
|
||||
loadConfig: () => ({}),
|
||||
},
|
||||
} as never);
|
||||
|
||||
const status = await matrixOnboardingAdapter.getStatus({
|
||||
cfg: {
|
||||
channels: {
|
||||
matrix: {
|
||||
defaultAccount: "ops",
|
||||
accounts: {
|
||||
ops: {
|
||||
homeserver: "https://matrix.ops.example.org",
|
||||
accessToken: "ops-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig,
|
||||
accountOverrides: {},
|
||||
});
|
||||
|
||||
expect(status.configured).toBe(true);
|
||||
expect(status.statusLines).toContain("Matrix: configured");
|
||||
expect(status.selectionHint).toBe("configured");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -493,7 +493,10 @@ async function runMatrixConfigure(params: {
|
||||
export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
channel,
|
||||
getStatus: async ({ cfg }) => {
|
||||
const account = resolveMatrixAccount({ cfg: cfg as CoreConfig });
|
||||
const account = resolveMatrixAccount({
|
||||
cfg: cfg as CoreConfig,
|
||||
accountId: resolveMatrixOnboardingAccountId(cfg as CoreConfig),
|
||||
});
|
||||
const configured = account.configured;
|
||||
const sdkReady = isMatrixSdkAvailable();
|
||||
return {
|
||||
|
||||
@@ -295,4 +295,27 @@ describe("matrix legacy encrypted-state migration", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("warns instead of throwing when a legacy crypto path is a file", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const stateDir = path.join(home, ".openclaw");
|
||||
writeFile(path.join(stateDir, "matrix", "crypto"), "not-a-directory");
|
||||
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
matrix: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "tok-123",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const detection = detectLegacyMatrixCrypto({ cfg, env: process.env });
|
||||
expect(detection.plans).toHaveLength(0);
|
||||
expect(detection.warnings).toContain(
|
||||
`Legacy Matrix encrypted state path exists but is not a directory: ${path.join(stateDir, "matrix", "crypto")}. OpenClaw skipped automatic crypto migration for that path.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,18 +87,50 @@ type MatrixStoredRecoveryKey = {
|
||||
};
|
||||
};
|
||||
|
||||
function isLegacyBotSdkCryptoStore(cryptoRootDir: string): boolean {
|
||||
return (
|
||||
fs.existsSync(path.join(cryptoRootDir, "bot-sdk.json")) ||
|
||||
fs.existsSync(path.join(cryptoRootDir, "matrix-sdk-crypto.sqlite3")) ||
|
||||
fs
|
||||
.readdirSync(cryptoRootDir, { withFileTypes: true })
|
||||
.some(
|
||||
(entry) =>
|
||||
entry.isDirectory() &&
|
||||
fs.existsSync(path.join(cryptoRootDir, entry.name, "matrix-sdk-crypto.sqlite3")),
|
||||
)
|
||||
);
|
||||
function detectLegacyBotSdkCryptoStore(cryptoRootDir: string): {
|
||||
detected: boolean;
|
||||
warning?: string;
|
||||
} {
|
||||
try {
|
||||
const stat = fs.statSync(cryptoRootDir);
|
||||
if (!stat.isDirectory()) {
|
||||
return {
|
||||
detected: false,
|
||||
warning:
|
||||
`Legacy Matrix encrypted state path exists but is not a directory: ${cryptoRootDir}. ` +
|
||||
"OpenClaw skipped automatic crypto migration for that path.",
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
detected: false,
|
||||
warning:
|
||||
`Failed reading legacy Matrix encrypted state path (${cryptoRootDir}): ${String(err)}. ` +
|
||||
"OpenClaw skipped automatic crypto migration for that path.",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
detected:
|
||||
fs.existsSync(path.join(cryptoRootDir, "bot-sdk.json")) ||
|
||||
fs.existsSync(path.join(cryptoRootDir, "matrix-sdk-crypto.sqlite3")) ||
|
||||
fs
|
||||
.readdirSync(cryptoRootDir, { withFileTypes: true })
|
||||
.some(
|
||||
(entry) =>
|
||||
entry.isDirectory() &&
|
||||
fs.existsSync(path.join(cryptoRootDir, entry.name, "matrix-sdk-crypto.sqlite3")),
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
detected: false,
|
||||
warning:
|
||||
`Failed scanning legacy Matrix encrypted state path (${cryptoRootDir}): ${String(err)}. ` +
|
||||
"OpenClaw skipped automatic crypto migration for that path.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function resolveMatrixAccountIds(cfg: OpenClawConfig): string[] {
|
||||
@@ -110,7 +142,14 @@ function resolveLegacyMatrixFlatStorePlan(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): MatrixLegacyCryptoPlan | { warning: string } | null {
|
||||
const legacy = resolveMatrixLegacyFlatStoragePaths(resolveStateDir(params.env, os.homedir));
|
||||
if (!fs.existsSync(legacy.cryptoPath) || !isLegacyBotSdkCryptoStore(legacy.cryptoPath)) {
|
||||
if (!fs.existsSync(legacy.cryptoPath)) {
|
||||
return null;
|
||||
}
|
||||
const legacyStore = detectLegacyBotSdkCryptoStore(legacy.cryptoPath);
|
||||
if (legacyStore.warning) {
|
||||
return { warning: legacyStore.warning };
|
||||
}
|
||||
if (!legacyStore.detected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -183,7 +222,15 @@ function resolveMatrixLegacyCryptoPlans(params: {
|
||||
continue;
|
||||
}
|
||||
const legacyCryptoPath = path.join(target.rootDir, "crypto");
|
||||
if (!fs.existsSync(legacyCryptoPath) || !isLegacyBotSdkCryptoStore(legacyCryptoPath)) {
|
||||
if (!fs.existsSync(legacyCryptoPath)) {
|
||||
continue;
|
||||
}
|
||||
const detectedStore = detectLegacyBotSdkCryptoStore(legacyCryptoPath);
|
||||
if (detectedStore.warning) {
|
||||
warnings.push(detectedStore.warning);
|
||||
continue;
|
||||
}
|
||||
if (!detectedStore.detected) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user