diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e7842b191..462cdf77c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ Docs: https://docs.openclaw.ai - Agents/exec: prevent gateway crash ("Agent listener invoked outside active run") when a subagent exec tool produces stdout/stderr after the agent run has ended or been aborted. (#62821) Thanks @openperf. - Browser/tabs: route `/tabs/action` close/select through the same browser endpoint reachability and policy checks as list/new (including Playwright-backed remote tab operations), reject CDP HTTP redirects on probe requests, and sanitize blocked-endpoint error responses so tab list/focus/close flows fail closed without echoing raw policy details back to callers. (#63332) - Gateway/OpenAI compat: return real `usage` for non-stream `/v1/chat/completions` responses, emit the final usage chunk when `stream_options.include_usage=true`, and bound usage-gated stream finalization after lifecycle end. (#62986) Thanks @Lellansin. +- Matrix/migration: keep packaged warning-only crypto migrations from being misclassified as actionable when only helper chunks are present, so startup and doctor stay on the warning-only path instead of creating unnecessary migration snapshots. (#64373) Thanks @gumadeiras. ## 2026.4.9 diff --git a/extensions/matrix/src/approval-native.test.ts b/extensions/matrix/src/approval-native.test.ts index ef5dd1b0bb3..45ea392a68c 100644 --- a/extensions/matrix/src/approval-native.test.ts +++ b/extensions/matrix/src/approval-native.test.ts @@ -22,7 +22,7 @@ function buildConfig( } as OpenClawConfig; } -describe("matrix native approval adapter", () => { +describe("matrix approval capability", () => { it("describes the correct Matrix exec-approval setup path", () => { const text = matrixApprovalCapability.describeExecApprovalSetup?.({ channel: "matrix", diff --git a/extensions/matrix/src/doctor.test.ts b/extensions/matrix/src/doctor.test.ts index ac4a00f825a..e13228efc59 100644 --- a/extensions/matrix/src/doctor.test.ts +++ b/extensions/matrix/src/doctor.test.ts @@ -18,11 +18,15 @@ vi.mock("./matrix-migration.runtime.js", async () => { ); return { ...actual, - hasActionableMatrixMigration: vi.fn(() => false), - hasPendingMatrixMigration: vi.fn(() => false), maybeCreateMatrixMigrationSnapshot: vi.fn(), autoMigrateLegacyMatrixState: vi.fn(async () => ({ changes: [], warnings: [] })), autoPrepareLegacyMatrixCrypto: vi.fn(async () => ({ changes: [], warnings: [] })), + resolveMatrixMigrationStatus: vi.fn(() => ({ + legacyState: null, + legacyCrypto: { warnings: [], plans: [] }, + pending: false, + actionable: false, + })), }; }); @@ -93,7 +97,12 @@ describe("matrix doctor", () => { it("surfaces matrix sequence warnings and repair changes", async () => { const runtimeApi = await import("./matrix-migration.runtime.js"); - vi.mocked(runtimeApi.hasActionableMatrixMigration).mockReturnValue(true); + vi.mocked(runtimeApi.resolveMatrixMigrationStatus).mockReturnValue({ + legacyState: null, + legacyCrypto: { warnings: [], plans: [] }, + pending: true, + actionable: true, + }); vi.mocked(runtimeApi.maybeCreateMatrixMigrationSnapshot).mockResolvedValue({ archivePath: "/tmp/matrix-backup.tgz", created: true, diff --git a/extensions/matrix/src/doctor.ts b/extensions/matrix/src/doctor.ts index 0b803cdc3d5..bca71c1bb6c 100644 --- a/extensions/matrix/src/doctor.ts +++ b/extensions/matrix/src/doctor.ts @@ -14,9 +14,8 @@ import { autoPrepareLegacyMatrixCrypto, detectLegacyMatrixCrypto, detectLegacyMatrixState, - hasActionableMatrixMigration, - hasPendingMatrixMigration, maybeCreateMatrixMigrationSnapshot, + resolveMatrixMigrationStatus, } from "./matrix-migration.runtime.js"; import { isRecord } from "./record-shared.js"; @@ -135,17 +134,13 @@ export async function applyMatrixDoctorRepair(params: { }): Promise<{ changes: string[]; warnings: string[] }> { const changes: string[] = []; const warnings: string[] = []; - const pendingMatrixMigration = hasPendingMatrixMigration({ - cfg: params.cfg, - env: params.env, - }); - const actionableMatrixMigration = hasActionableMatrixMigration({ + const migrationStatus = resolveMatrixMigrationStatus({ cfg: params.cfg, env: params.env, }); let matrixSnapshotReady = true; - if (actionableMatrixMigration) { + if (migrationStatus.actionable) { try { const snapshot = await maybeCreateMatrixMigrationSnapshot({ trigger: "doctor-fix", @@ -163,7 +158,7 @@ export async function applyMatrixDoctorRepair(params: { '- Skipping Matrix migration changes for now. Resolve the snapshot failure, then rerun "openclaw doctor --fix".', ); } - } else if (pendingMatrixMigration) { + } else if (migrationStatus.pending) { warnings.push( "- Matrix migration warnings are present, but no on-disk Matrix mutation is actionable yet. No pre-migration snapshot was needed.", ); @@ -224,15 +219,6 @@ export async function runMatrixDoctorSequence(params: { return { changeNotes, warningNotes }; } - const legacyState = detectLegacyMatrixState({ - cfg: params.cfg, - env: params.env, - }); - const legacyCrypto = detectLegacyMatrixCrypto({ - cfg: params.cfg, - env: params.env, - }); - if (params.shouldRepair) { const repair = await applyMatrixDoctorRepair({ cfg: params.cfg, @@ -240,16 +226,24 @@ export async function runMatrixDoctorSequence(params: { }); changeNotes.push(...repair.changes); warningNotes.push(...repair.warnings); - } else if (legacyState) { - if ("warning" in legacyState) { - warningNotes.push(`- ${legacyState.warning}`); - } else { - warningNotes.push(formatMatrixLegacyStatePreview(legacyState)); + } else { + const migrationStatus = resolveMatrixMigrationStatus({ + cfg: params.cfg, + env: params.env, + }); + if (migrationStatus.legacyState) { + if ("warning" in migrationStatus.legacyState) { + warningNotes.push(`- ${migrationStatus.legacyState.warning}`); + } else { + warningNotes.push(formatMatrixLegacyStatePreview(migrationStatus.legacyState)); + } + } + if ( + migrationStatus.legacyCrypto.warnings.length > 0 || + migrationStatus.legacyCrypto.plans.length > 0 + ) { + warningNotes.push(...formatMatrixLegacyCryptoPreview(migrationStatus.legacyCrypto)); } - } - - if (!params.shouldRepair && (legacyCrypto.warnings.length > 0 || legacyCrypto.plans.length > 0)) { - warningNotes.push(...formatMatrixLegacyCryptoPreview(legacyCrypto)); } return { changeNotes, warningNotes }; diff --git a/extensions/matrix/src/legacy-crypto-inspector-availability.test.ts b/extensions/matrix/src/legacy-crypto-inspector-availability.test.ts new file mode 100644 index 00000000000..9cf472f6028 --- /dev/null +++ b/extensions/matrix/src/legacy-crypto-inspector-availability.test.ts @@ -0,0 +1,81 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const availabilityState = vi.hoisted(() => ({ + currentFilePath: "/virtual/dist/matrix-migration.runtime.js", + existingPaths: new Set(), + dirEntries: [] as Array<{ name: string; isFile: () => boolean }>, +})); + +vi.mock("node:fs", async () => { + const { mockNodeBuiltinModule } = await import("../../../test/helpers/node-builtin-mocks.js"); + return mockNodeBuiltinModule( + () => vi.importActual("node:fs"), + { + existsSync: (candidate: unknown) => availabilityState.existingPaths.has(String(candidate)), + readdirSync: () => availabilityState.dirEntries as never, + }, + { mirrorToDefault: true }, + ); +}); + +vi.mock("node:url", async () => { + const actual = await vi.importActual("node:url"); + return { + ...actual, + fileURLToPath: () => availabilityState.currentFilePath, + }; +}); + +const { isMatrixLegacyCryptoInspectorAvailable } = + await import("./legacy-crypto-inspector-availability.js"); + +describe("isMatrixLegacyCryptoInspectorAvailable", () => { + beforeEach(() => { + availabilityState.currentFilePath = "/virtual/dist/matrix-migration.runtime.js"; + availabilityState.existingPaths.clear(); + availabilityState.dirEntries = []; + }); + + it("detects the source inspector module directly", () => { + availabilityState.currentFilePath = + "/virtual/extensions/matrix/src/legacy-crypto-inspector-availability.js"; + availabilityState.existingPaths.add( + "/virtual/extensions/matrix/src/matrix/legacy-crypto-inspector.ts", + ); + + expect(isMatrixLegacyCryptoInspectorAvailable()).toBe(true); + }); + + it("detects hashed built inspector chunks", () => { + availabilityState.dirEntries = [ + { + name: "legacy-crypto-inspector-TPlLnFSE.js", + isFile: () => true, + }, + ]; + + expect(isMatrixLegacyCryptoInspectorAvailable()).toBe(true); + }); + + it("does not confuse the availability helper artifact with the real inspector", () => { + availabilityState.dirEntries = [ + { + name: "legacy-crypto-inspector-availability.js", + isFile: () => true, + }, + ]; + + expect(isMatrixLegacyCryptoInspectorAvailable()).toBe(false); + }); + + it("does not confuse hashed availability helper chunks with the real inspector", () => { + availabilityState.dirEntries = [ + { + name: "legacy-crypto-inspector-availability-TPlLnFSE.js", + isFile: () => true, + }, + ]; + + expect(isMatrixLegacyCryptoInspectorAvailable()).toBe(false); + }); +}); diff --git a/extensions/matrix/src/legacy-crypto-inspector-availability.ts b/extensions/matrix/src/legacy-crypto-inspector-availability.ts index 45d0bc9cb2c..bc7f229e58b 100644 --- a/extensions/matrix/src/legacy-crypto-inspector-availability.ts +++ b/extensions/matrix/src/legacy-crypto-inspector-availability.ts @@ -2,7 +2,31 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; -const LEGACY_CRYPTO_INSPECTOR_BASENAME_RE = /^legacy-crypto-inspector(?:[-.].*)?\.js$/u; +const LEGACY_CRYPTO_INSPECTOR_FILE = "legacy-crypto-inspector.js"; +const LEGACY_CRYPTO_INSPECTOR_CHUNK_PREFIX = "legacy-crypto-inspector-"; +const LEGACY_CRYPTO_INSPECTOR_HELPER_CHUNK_PREFIX = "availability-"; +const JAVASCRIPT_MODULE_SUFFIX = ".js"; + +function isLegacyCryptoInspectorArtifactName(name: string): boolean { + if (name === LEGACY_CRYPTO_INSPECTOR_FILE) { + return true; + } + if ( + !name.startsWith(LEGACY_CRYPTO_INSPECTOR_CHUNK_PREFIX) || + !name.endsWith(JAVASCRIPT_MODULE_SUFFIX) + ) { + return false; + } + const chunkSuffix = name.slice( + LEGACY_CRYPTO_INSPECTOR_CHUNK_PREFIX.length, + -JAVASCRIPT_MODULE_SUFFIX.length, + ); + return ( + chunkSuffix.length > 0 && + chunkSuffix !== "availability" && + !chunkSuffix.startsWith(LEGACY_CRYPTO_INSPECTOR_HELPER_CHUNK_PREFIX) + ); +} function hasSourceInspectorArtifact(currentDir: string): boolean { return [ @@ -20,7 +44,7 @@ function hasBuiltInspectorArtifact(currentDir: string): boolean { } return fs .readdirSync(currentDir, { withFileTypes: true }) - .some((entry) => entry.isFile() && LEGACY_CRYPTO_INSPECTOR_BASENAME_RE.test(entry.name)); + .some((entry) => entry.isFile() && isLegacyCryptoInspectorArtifactName(entry.name)); } export function isMatrixLegacyCryptoInspectorAvailable(): boolean { diff --git a/extensions/matrix/src/legacy-crypto.test.ts b/extensions/matrix/src/legacy-crypto.test.ts index c85aea4f7e0..e161bdec104 100644 --- a/extensions/matrix/src/legacy-crypto.test.ts +++ b/extensions/matrix/src/legacy-crypto.test.ts @@ -92,6 +92,7 @@ describe("matrix legacy encrypted-state migration", () => { const { cfg, rootDir } = writeDefaultLegacyCryptoFixture(home); const detection = detectLegacyMatrixCrypto({ cfg, env: process.env }); + expect(detection.inspectorAvailable).toBe(true); expect(detection.warnings).toEqual([]); expect(detection.plans).toHaveLength(1); @@ -210,6 +211,7 @@ describe("matrix legacy encrypted-state migration", () => { const { cfg } = writeDefaultLegacyCryptoFixture(home); const detection = detectLegacyMatrixCrypto({ cfg, env: process.env }); + expect(detection.inspectorAvailable).toBe(false); expect(detection.plans).toHaveLength(1); expect(detection.warnings).toContain( "Legacy Matrix encrypted state was detected, but the Matrix crypto inspector is unavailable.", diff --git a/extensions/matrix/src/legacy-crypto.ts b/extensions/matrix/src/legacy-crypto.ts index 7914807346a..da578c4f9c2 100644 --- a/extensions/matrix/src/legacy-crypto.ts +++ b/extensions/matrix/src/legacy-crypto.ts @@ -57,6 +57,7 @@ type MatrixLegacyCryptoPlan = { }; type MatrixLegacyCryptoDetection = { + inspectorAvailable: boolean; plans: MatrixLegacyCryptoPlan[]; warnings: string[]; }; @@ -324,13 +325,20 @@ export function detectLegacyMatrixCrypto(params: { cfg: params.cfg, env: params.env ?? process.env, }); - if (detection.plans.length > 0 && !isMatrixLegacyCryptoInspectorAvailable()) { + const inspectorAvailable = + detection.plans.length === 0 || isMatrixLegacyCryptoInspectorAvailable(); + if (!inspectorAvailable && detection.plans.length > 0) { return { + inspectorAvailable, plans: detection.plans, warnings: [...detection.warnings, MATRIX_LEGACY_CRYPTO_INSPECTOR_UNAVAILABLE_MESSAGE], }; } - return detection; + return { + inspectorAvailable, + plans: detection.plans, + warnings: detection.warnings, + }; } export async function autoPrepareLegacyMatrixCrypto(params: { @@ -359,7 +367,7 @@ export async function autoPrepareLegacyMatrixCrypto(params: { warnings, }; } - if (!params.deps?.inspectLegacyStore && !isMatrixLegacyCryptoInspectorAvailable()) { + if (!params.deps?.inspectLegacyStore && !detection.inspectorAvailable) { if (warnings.length > 0) { params.log?.warn?.( `matrix: legacy encrypted-state warnings:\n${warnings.map((entry) => `- ${entry}`).join("\n")}`, diff --git a/extensions/matrix/src/matrix-migration.runtime.ts b/extensions/matrix/src/matrix-migration.runtime.ts index 366a4d25001..b163f2fbb19 100644 --- a/extensions/matrix/src/matrix-migration.runtime.ts +++ b/extensions/matrix/src/matrix-migration.runtime.ts @@ -1,4 +1,9 @@ export { autoMigrateLegacyMatrixState, detectLegacyMatrixState } from "./legacy-state.js"; export { autoPrepareLegacyMatrixCrypto, detectLegacyMatrixCrypto } from "./legacy-crypto.js"; -export { hasActionableMatrixMigration, hasPendingMatrixMigration } from "./migration-snapshot.js"; +export { + hasActionableMatrixMigration, + hasPendingMatrixMigration, + resolveMatrixMigrationStatus, + type MatrixMigrationStatus, +} from "./migration-snapshot.js"; export { maybeCreateMatrixMigrationSnapshot } from "./migration-snapshot-backup.js"; diff --git a/extensions/matrix/src/migration-snapshot.test.ts b/extensions/matrix/src/migration-snapshot.test.ts index 882bf2834c8..172de40fe64 100644 --- a/extensions/matrix/src/migration-snapshot.test.ts +++ b/extensions/matrix/src/migration-snapshot.test.ts @@ -124,6 +124,7 @@ describe("matrix migration snapshots", () => { cfg, env: process.env, }); + expect(detection.inspectorAvailable).toBe(true); expect(detection.plans).toHaveLength(1); expect(detection.warnings).toEqual([]); expect( @@ -167,6 +168,7 @@ describe("matrix migration snapshots", () => { cfg, env: process.env, }); + expect(detection.inspectorAvailable).toBe(false); expect(detection.plans).toHaveLength(1); expect(detection.warnings).toContain( "Legacy Matrix encrypted state was detected, but the Matrix crypto inspector is unavailable.", diff --git a/extensions/matrix/src/migration-snapshot.ts b/extensions/matrix/src/migration-snapshot.ts index 1cf074ed249..c5a518cf825 100644 --- a/extensions/matrix/src/migration-snapshot.ts +++ b/extensions/matrix/src/migration-snapshot.ts @@ -1,5 +1,4 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; -import { isMatrixLegacyCryptoInspectorAvailable } from "./legacy-crypto-inspector-availability.js"; import { detectLegacyMatrixCrypto } from "./legacy-crypto.js"; import { detectLegacyMatrixState } from "./legacy-state.js"; import { @@ -9,30 +8,43 @@ import { type MatrixMigrationSnapshotResult, } from "./migration-snapshot-backup.js"; +export type MatrixMigrationStatus = { + legacyState: ReturnType; + legacyCrypto: ReturnType; + pending: boolean; + actionable: boolean; +}; + +export function resolveMatrixMigrationStatus(params: { + cfg: OpenClawConfig; + env?: NodeJS.ProcessEnv; +}): MatrixMigrationStatus { + const env = params.env ?? process.env; + const legacyState = detectLegacyMatrixState({ cfg: params.cfg, env }); + const legacyCrypto = detectLegacyMatrixCrypto({ cfg: params.cfg, env }); + const actionableLegacyState = legacyState !== null && !("warning" in legacyState); + const actionableLegacyCrypto = legacyCrypto.plans.length > 0 && legacyCrypto.inspectorAvailable; + return { + legacyState, + legacyCrypto, + pending: + legacyState !== null || legacyCrypto.plans.length > 0 || legacyCrypto.warnings.length > 0, + actionable: actionableLegacyState || actionableLegacyCrypto, + }; +} + export function hasPendingMatrixMigration(params: { cfg: OpenClawConfig; env?: NodeJS.ProcessEnv; }): boolean { - const env = params.env ?? process.env; - const legacyState = detectLegacyMatrixState({ cfg: params.cfg, env }); - if (legacyState) { - return true; - } - const legacyCrypto = detectLegacyMatrixCrypto({ cfg: params.cfg, env }); - return legacyCrypto.plans.length > 0 || legacyCrypto.warnings.length > 0; + return resolveMatrixMigrationStatus(params).pending; } export function hasActionableMatrixMigration(params: { cfg: OpenClawConfig; env?: NodeJS.ProcessEnv; }): boolean { - const env = params.env ?? process.env; - const legacyState = detectLegacyMatrixState({ cfg: params.cfg, env }); - if (legacyState && !("warning" in legacyState)) { - return true; - } - const legacyCrypto = detectLegacyMatrixCrypto({ cfg: params.cfg, env }); - return legacyCrypto.plans.length > 0 && isMatrixLegacyCryptoInspectorAvailable(); + return resolveMatrixMigrationStatus(params).actionable; } export { diff --git a/extensions/matrix/src/startup-maintenance.test.ts b/extensions/matrix/src/startup-maintenance.test.ts index 1dab534a7f4..491245738d4 100644 --- a/extensions/matrix/src/startup-maintenance.test.ts +++ b/extensions/matrix/src/startup-maintenance.test.ts @@ -1,8 +1,18 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { withTempHome } from "../../../test/helpers/temp-home.js"; + +const legacyCryptoInspectorAvailability = vi.hoisted(() => ({ + available: true, +})); + +vi.mock("./legacy-crypto-inspector-availability.js", () => ({ + isMatrixLegacyCryptoInspectorAvailable: () => legacyCryptoInspectorAvailability.available, +})); + import { runMatrixStartupMaintenance } from "./startup-maintenance.js"; +import { resolveMatrixAccountStorageRoot } from "./storage-paths.js"; async function seedLegacyMatrixState(home: string) { const stateDir = path.join(home, ".openclaw"); @@ -26,6 +36,22 @@ function makeMatrixStartupConfig(includeCredentials = true) { } as const; } +async function seedLegacyMatrixCrypto(home: string) { + const stateDir = path.join(home, ".openclaw"); + const { rootDir } = resolveMatrixAccountStorageRoot({ + stateDir, + homeserver: "https://matrix.example.org", + userId: "@bot:example.org", + accessToken: "tok-123", + }); + await fs.mkdir(path.join(rootDir, "crypto"), { recursive: true }); + await fs.writeFile( + path.join(rootDir, "crypto", "bot-sdk.json"), + JSON.stringify({ deviceId: "DEVICE123" }), + "utf8", + ); +} + function createSuccessfulMatrixMigrationDeps() { return { maybeCreateMatrixMigrationSnapshot: vi.fn(async () => ({ @@ -42,6 +68,10 @@ function createSuccessfulMatrixMigrationDeps() { } describe("runMatrixStartupMaintenance", () => { + beforeEach(() => { + legacyCryptoInspectorAvailability.available = true; + }); + it("creates a snapshot before actionable startup migration", async () => { await withTempHome(async (home) => { await seedLegacyMatrixState(home); @@ -78,6 +108,7 @@ describe("runMatrixStartupMaintenance", () => { const autoMigrateLegacyMatrixStateMock = vi.fn(); const autoPrepareLegacyMatrixCryptoMock = vi.fn(); const info = vi.fn(); + const warn = vi.fn(); await runMatrixStartupMaintenance({ cfg: makeMatrixStartupConfig(false), @@ -87,7 +118,7 @@ describe("runMatrixStartupMaintenance", () => { autoMigrateLegacyMatrixState: autoMigrateLegacyMatrixStateMock as never, autoPrepareLegacyMatrixCrypto: autoPrepareLegacyMatrixCryptoMock as never, }, - log: { info }, + log: { info, warn }, }); expect(maybeCreateMatrixMigrationSnapshotMock).not.toHaveBeenCalled(); @@ -96,6 +127,41 @@ describe("runMatrixStartupMaintenance", () => { expect(info).toHaveBeenCalledWith( "matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet", ); + expect(warn).toHaveBeenCalledWith(expect.stringContaining("could not be resolved yet")); + }); + }); + + it("logs the concrete unavailable-inspector warning when startup migration is warning-only", async () => { + legacyCryptoInspectorAvailability.available = false; + + await withTempHome(async (home) => { + await seedLegacyMatrixCrypto(home); + const maybeCreateMatrixMigrationSnapshotMock = vi.fn(); + const autoMigrateLegacyMatrixStateMock = vi.fn(); + const autoPrepareLegacyMatrixCryptoMock = vi.fn(); + const info = vi.fn(); + const warn = vi.fn(); + + await runMatrixStartupMaintenance({ + cfg: makeMatrixStartupConfig(), + env: process.env, + deps: { + maybeCreateMatrixMigrationSnapshot: maybeCreateMatrixMigrationSnapshotMock as never, + autoMigrateLegacyMatrixState: autoMigrateLegacyMatrixStateMock as never, + autoPrepareLegacyMatrixCrypto: autoPrepareLegacyMatrixCryptoMock as never, + }, + log: { info, warn }, + }); + + expect(maybeCreateMatrixMigrationSnapshotMock).not.toHaveBeenCalled(); + expect(autoMigrateLegacyMatrixStateMock).not.toHaveBeenCalled(); + expect(autoPrepareLegacyMatrixCryptoMock).not.toHaveBeenCalled(); + expect(info).toHaveBeenCalledWith( + "matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet", + ); + expect(warn).toHaveBeenCalledWith( + "matrix: legacy encrypted-state warnings:\n- Legacy Matrix encrypted state was detected, but the Matrix crypto inspector is unavailable.", + ); }); }); diff --git a/extensions/matrix/src/startup-maintenance.ts b/extensions/matrix/src/startup-maintenance.ts index eaee1315ab4..1d29cd01577 100644 --- a/extensions/matrix/src/startup-maintenance.ts +++ b/extensions/matrix/src/startup-maintenance.ts @@ -2,9 +2,9 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { autoMigrateLegacyMatrixState, autoPrepareLegacyMatrixCrypto, - hasActionableMatrixMigration, - hasPendingMatrixMigration, maybeCreateMatrixMigrationSnapshot, + resolveMatrixMigrationStatus, + type MatrixMigrationStatus, } from "./matrix-migration.runtime.js"; type MatrixStartupLogger = { @@ -12,6 +12,21 @@ type MatrixStartupLogger = { warn?: (message: string) => void; }; +function logWarningOnlyMatrixMigrationReasons(params: { + status: MatrixMigrationStatus; + log: MatrixStartupLogger; +}): void { + if (params.status.legacyState && "warning" in params.status.legacyState) { + params.log.warn?.(`matrix: ${params.status.legacyState.warning}`); + } + + if (params.status.legacyCrypto.warnings.length > 0) { + params.log.warn?.( + `matrix: legacy encrypted-state warnings:\n${params.status.legacyCrypto.warnings.map((entry) => `- ${entry}`).join("\n")}`, + ); + } +} + async function runBestEffortMatrixMigrationStep(params: { label: string; log: MatrixStartupLogger; @@ -48,16 +63,16 @@ export async function runMatrixStartupMaintenance(params: { params.deps?.autoPrepareLegacyMatrixCrypto ?? autoPrepareLegacyMatrixCrypto; const trigger = params.trigger?.trim() || "gateway-startup"; const logPrefix = params.logPrefix?.trim() || "gateway"; - const actionable = hasActionableMatrixMigration({ cfg: params.cfg, env }); - const pending = actionable || hasPendingMatrixMigration({ cfg: params.cfg, env }); + const migrationStatus = resolveMatrixMigrationStatus({ cfg: params.cfg, env }); - if (!pending) { + if (!migrationStatus.pending) { return; } - if (!actionable) { + if (!migrationStatus.actionable) { params.log.info?.( "matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet", ); + logWarningOnlyMatrixMigrationReasons({ status: migrationStatus, log: params.log }); return; } diff --git a/src/plugin-sdk/matrix-runtime-heavy.ts b/src/plugin-sdk/matrix-runtime-heavy.ts index 21849426d22..e353cbe9af4 100644 --- a/src/plugin-sdk/matrix-runtime-heavy.ts +++ b/src/plugin-sdk/matrix-runtime-heavy.ts @@ -19,6 +19,7 @@ type MatrixLegacyCryptoPlan = { }; type MatrixLegacyCryptoDetection = { + inspectorAvailable: boolean; plans: MatrixLegacyCryptoPlan[]; warnings: string[]; };