mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
fix(matrix): harden recovery QA diagnostics
This commit is contained in:
@@ -543,6 +543,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Slack: route native stream fallback replies through the normal chunked sender so long buffered Slack Connect responses are not dropped or duplicated. (#71124) Thanks @martingarramon.
|
||||
- WhatsApp: transcribe accepted voice notes before agent dispatch while keeping spoken transcripts out of command authorization. (#64120) Thanks @rogerdigital.
|
||||
- Plugins/CLI: expose channel plugin CLI descriptors during discovery-mode plugin loads so snapshot registries keep channel commands visible without activating full runtimes. (#71309) Thanks @gumadeiras.
|
||||
- Matrix: separate recovery-key, backup, and owner-trust diagnostics during E2EE recovery, add recovery-key rotation for backup reset, and cover destructive backup restore paths in QA. (#71311) Thanks @gumadeiras.
|
||||
- WhatsApp: deliver media generated by tool-result replies while still suppressing text-only tool chatter. (#60968) Thanks @adaclaw.
|
||||
- Config/agents: accept `agents.list[].contextTokens` in strict config validation so per-agent overrides survive hot reload, letting `/status` reflect the configured model window instead of the 200k fallback. Fixes #70692. (#71247) Thanks @statxc.
|
||||
- Heartbeat: include async exec completion details in heartbeat prompts so command-finished notifications relay the actual output. (#71213) Thanks @GodsBoy.
|
||||
|
||||
@@ -1579,6 +1579,36 @@ describe("MatrixClient crypto bootstrapping", () => {
|
||||
expect(status.serverDeviceKnown).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps verification diagnostics when the homeserver device list cannot be read", async () => {
|
||||
matrixJsClient.getUserId = vi.fn(() => "@bot:example.org");
|
||||
matrixJsClient.getDeviceId = vi.fn(() => "DEVICE123");
|
||||
matrixJsClient.getDevices = vi.fn(async () => {
|
||||
throw new Error("device list unavailable");
|
||||
});
|
||||
matrixJsClient.getCrypto = vi.fn(() => ({
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage: vi.fn(async () => {}),
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
localVerified: true,
|
||||
crossSigningVerified: true,
|
||||
signedByOwner: true,
|
||||
})),
|
||||
}));
|
||||
|
||||
const client = new MatrixClient("https://matrix.example.org", "token", {
|
||||
encryption: true,
|
||||
});
|
||||
await client.start();
|
||||
|
||||
const status = await client.getOwnDeviceVerificationStatus();
|
||||
expect(status.verified).toBe(true);
|
||||
expect(status.backup).toBeDefined();
|
||||
expect(status.serverDeviceKnown).toBeNull();
|
||||
});
|
||||
|
||||
it("does not treat local-only trust as Matrix identity trust", async () => {
|
||||
matrixJsClient.getUserId = vi.fn(() => "@bot:example.org");
|
||||
matrixJsClient.getDeviceId = vi.fn(() => "DEVICE123");
|
||||
|
||||
@@ -1130,10 +1130,10 @@ export class MatrixClient {
|
||||
const [backup, deviceVerification, ownDevices] = await Promise.all([
|
||||
this.getRoomKeyBackupStatus(),
|
||||
this.getDeviceVerificationStatus(userId, deviceId),
|
||||
this.listOwnDevices(),
|
||||
this.listOwnDevices().catch(() => null),
|
||||
]);
|
||||
const serverDeviceKnown = deviceId
|
||||
? ownDevices.some((device) => device.deviceId === deviceId)
|
||||
? (ownDevices?.some((device) => device.deviceId === deviceId) ?? null)
|
||||
: null;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { copyFile, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { chmod, copyFile, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import type { MatrixVerificationSummary } from "@openclaw/matrix/test-api.js";
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "./scenario-catalog.js";
|
||||
import {
|
||||
createMatrixQaOpenClawCliRuntime,
|
||||
formatMatrixQaCliCommand,
|
||||
redactMatrixQaCliOutput,
|
||||
type MatrixQaCliRunResult,
|
||||
} from "./scenario-runtime-cli.js";
|
||||
@@ -273,13 +274,13 @@ function parseMatrixQaCliJson(result: MatrixQaCliRunResult): unknown {
|
||||
const stderr = result.stderr.trim();
|
||||
const payload = stdout || stderr;
|
||||
if (!payload) {
|
||||
throw new Error(`openclaw ${result.args.join(" ")} did not print JSON`);
|
||||
throw new Error(`${formatMatrixQaCliCommand(result.args)} did not print JSON`);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(payload) as unknown;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`openclaw ${result.args.join(" ")} printed invalid JSON: ${
|
||||
`${formatMatrixQaCliCommand(result.args)} printed invalid JSON: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}\n${redactMatrixQaCliOutput(payload)}`,
|
||||
{ cause: error },
|
||||
@@ -603,10 +604,14 @@ async function mutateMatrixQaCliStateLoss(params: {
|
||||
}) {
|
||||
const accountRoot = await findMatrixQaCliAccountRoot(params);
|
||||
const recoveryKeyPath = path.join(accountRoot, "recovery-key.json");
|
||||
const preservedRecoveryKeyPath = path.join(params.runtime.rootDir, "preserved-recovery-key.json");
|
||||
const preservedRecoveryKeyPath = path.join(
|
||||
params.runtime.stateDir,
|
||||
"preserved-recovery-key.json",
|
||||
);
|
||||
let recoveryKeyPreserved = false;
|
||||
if (params.preserveRecoveryKey) {
|
||||
await copyFile(recoveryKeyPath, preservedRecoveryKeyPath);
|
||||
await chmod(preservedRecoveryKeyPath, 0o600).catch(() => undefined);
|
||||
recoveryKeyPreserved = true;
|
||||
}
|
||||
await rm(accountRoot, { force: true, recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user