diff --git a/.secrets.baseline b/.secrets.baseline index 5a0c639b9e3..6ac5c7ca2ec 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -205,7 +205,7 @@ "filename": "apps/macos/Sources/OpenClawProtocol/GatewayModels.swift", "hashed_secret": "7990585255d25249fb1e6eac3d2bd6c37429b2cd", "is_verified": false, - "line_number": 1859 + "line_number": 1763 } ], "apps/macos/Tests/OpenClawIPCTests/AnthropicAuthResolverTests.swift": [ @@ -266,7 +266,7 @@ "filename": "apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift", "hashed_secret": "7990585255d25249fb1e6eac3d2bd6c37429b2cd", "is_verified": false, - "line_number": 1859 + "line_number": 1763 } ], "docs/.i18n/zh-CN.tm.jsonl": [ @@ -9626,15 +9626,6 @@ "line_number": 65 } ], - "docs/channels/matrix.md": [ - { - "type": "Secret Keyword", - "filename": "docs/channels/matrix.md", - "hashed_secret": "45d676e7c6ab44cf4b8fa366ef2d8fccd3e6d6e6", - "is_verified": false, - "line_number": 60 - } - ], "docs/channels/nextcloud-talk.md": [ { "type": "Secret Keyword", @@ -9774,63 +9765,63 @@ "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "1188d5a8ed7edcff5144a9472af960243eacf12e", "is_verified": false, - "line_number": 1614 + "line_number": 1615 }, { "type": "Secret Keyword", "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "bde4db9b4c3be4049adc3b9a69851d7c35119770", "is_verified": false, - "line_number": 1630 + "line_number": 1631 }, { "type": "Secret Keyword", "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "7f8aaf142ce0552c260f2e546dda43ddd7c9aef3", "is_verified": false, - "line_number": 1817 + "line_number": 1818 }, { "type": "Secret Keyword", "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "22af290a1a3d5e941193a41a3d3a9e4ca8da5e27", "is_verified": false, - "line_number": 1990 + "line_number": 1991 }, { "type": "Secret Keyword", "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "ec3810e10fb78db55ce38b9c18d1c3eb1db739e0", "is_verified": false, - "line_number": 2046 + "line_number": 2047 }, { "type": "Secret Keyword", "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "c1e6ee547fd492df1441ac492e8bb294974712bd", "is_verified": false, - "line_number": 2278 + "line_number": 2279 }, { "type": "Secret Keyword", "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "45d676e7c6ab44cf4b8fa366ef2d8fccd3e6d6e6", "is_verified": false, - "line_number": 2408 + "line_number": 2409 }, { "type": "Secret Keyword", "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "a219d7693c25cd2d93313512e200ff3eb374d281", "is_verified": false, - "line_number": 2661 + "line_number": 2662 }, { "type": "Secret Keyword", "filename": "docs/gateway/configuration-reference.md", "hashed_secret": "b6f56e5e92078ed7c078c46fbfeedcbe5719bc25", "is_verified": false, - "line_number": 2663 + "line_number": 2664 } ], "docs/gateway/configuration.md": [ @@ -11048,7 +11039,7 @@ "filename": "extensions/matrix/src/matrix/accounts.test.ts", "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", "is_verified": false, - "line_number": 74 + "line_number": 78 } ], "extensions/matrix/src/matrix/client.test.ts": [ @@ -11057,14 +11048,14 @@ "filename": "extensions/matrix/src/matrix/client.test.ts", "hashed_secret": "fe7fcdaea49ece14677acd32374d2f1225819d5c", "is_verified": false, - "line_number": 13 + "line_number": 24 }, { "type": "Secret Keyword", "filename": "extensions/matrix/src/matrix/client.test.ts", "hashed_secret": "3dc927d80543dc0f643940b70d066bd4b4c4b78e", "is_verified": false, - "line_number": 23 + "line_number": 34 } ], "extensions/matrix/src/matrix/client/storage.ts": [ @@ -11073,7 +11064,7 @@ "filename": "extensions/matrix/src/matrix/client/storage.ts", "hashed_secret": "7505d64a54e061b7acd54ccd58b49dc43500b635", "is_verified": false, - "line_number": 8 + "line_number": 11 } ], "extensions/memory-lancedb/config.ts": [ @@ -11659,7 +11650,7 @@ "filename": "src/agents/tools/web-search.ts", "hashed_secret": "dfba7aade0868074c2861c98e2a9a92f3178a51b", "is_verified": false, - "line_number": 291 + "line_number": 292 } ], "src/agents/tools/web-tools.enabled-defaults.e2e.test.ts": [ @@ -13013,5 +13004,5 @@ } ] }, - "generated_at": "2026-03-10T03:11:06Z" + "generated_at": "2026-03-09T12:14:50Z" } diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md index 16c7cd505a2..af623fbbe69 100644 --- a/docs/channels/matrix.md +++ b/docs/channels/matrix.md @@ -87,7 +87,7 @@ Password-based setup (token is cached after login): enabled: true, homeserver: "https://matrix.example.org", userId: "@bot:example.org", - password: "replace-me", + password: "replace-me", // pragma: allowlist secret deviceName: "OpenClaw Gateway", }, }, diff --git a/docs/experiments/plans/matrix-supersession-migration.md b/docs/experiments/plans/matrix-supersession-migration.md deleted file mode 100644 index dded21d5e39..00000000000 --- a/docs/experiments/plans/matrix-supersession-migration.md +++ /dev/null @@ -1,212 +0,0 @@ ---- -summary: "Replace the legacy Matrix plugin with the new Matrix implementation while preserving the public matrix surface and providing automatic migration for current users." -owner: "gumadeiras" -status: "implemented" -last_updated: "2026-03-08" -title: "Matrix Supersession Migration" ---- - -# Matrix Supersession Migration - -## Overview - -This plan replaces the current public `matrix` plugin with the newer Matrix implementation that currently lives under `matrix-js`. - -The external result should feel like an in-place upgrade for existing Matrix users: - -- package stays `@openclaw/matrix` -- plugin/channel/binding id stays `matrix` -- config stays under `channels.matrix` -- existing public Matrix state stays canonical -- automatic migration handles everything deterministic -- non-deterministic cases surface clear, exact next steps - -This plan is also the working implementation tracker. Update the checklist statuses as tasks land. - -## Progress tracker - -### Current status - -- [x] Migration plan written and tracked in repo -- [x] Replace `extensions/matrix` with the new implementation -- [x] Remove shipped `matrix-js` public/runtime/package surfaces -- [x] Preserve legacy `matrix` config compatibility -- [x] Preserve legacy `matrix` state compatibility -- [x] Add startup and doctor migration/repair UX -- [x] Rewrite docs/help/tests to use `matrix` only -- [x] Verify update flow end to end - -### Change log - -- 2026-03-08: Initial supersession plan written and added to docs as a live checklist. -- 2026-03-08: Replaced `extensions/matrix` with the new Matrix implementation, removed shipped `matrix-js` surfaces, added startup/doctor Matrix migration UX, and deleted `extensions/matrix-js`. -- 2026-03-08: Added encrypted-state migration prep for legacy Matrix rust crypto stores, automatic backup-key extraction, startup room-key restore, and explicit warnings for local-only keys that cannot be exported automatically. - -## Summary - -- Replace the current `extensions/matrix` implementation with the current Matrix-js implementation, then delete `extensions/matrix-js`. -- Ship the new implementation only as `matrix`: same npm package (`@openclaw/matrix`), same plugin id (`matrix`), same channel id (`matrix`), same config key (`channels.matrix`), same docs path (`/channels/matrix`), same local install path (`extensions/matrix`). -- Do not ship any `matrix-js` runtime compatibility aliases, config aliases, CLI aliases, gateway-method aliases, or package aliases. -- Preserve existing public `matrix` user configs and state as the source of truth. The migration should feel like an in-place upgrade, not a channel rename. -- Use automatic repair on startup and in doctor/update. If repair cannot be done safely, emit explicit, actionable messaging. - -## Public surface after cutover - -- [x] Canonical package/install surface stays `@openclaw/matrix` and `openclaw plugins install @openclaw/matrix`. -- [x] Canonical channel/plugin/binding id is `matrix`. -- [x] Canonical config namespace is `channels.matrix`. -- [x] Canonical CLI surface is `openclaw matrix ...`, including the verification/account commands currently only exposed under Matrix-js. -- [x] Canonical gateway methods become `matrix.verify.status`, `matrix.verify.bootstrap`, and `matrix.verify.recoveryKey`. -- [x] Canonical ACP/subagent binding channel is `matrix`. -- [x] Canonical plugin SDK subpath is `openclaw/plugin-sdk/matrix`. -- [x] Remove all shipped/public `matrix-js` references from docs, config help, tests, install catalog metadata, and package exports. - -## Migration flow and UX - -### Standard npm users - -- [x] No config key change required. -- [x] No plugin install record rewrite required because the package remains `@openclaw/matrix`. -- [x] Updating OpenClaw or running `openclaw plugins update` replaces the plugin in place. -- [x] Startup and doctor automatically repair any legacy Matrix config/state that the new implementation cannot consume directly. - -### Startup behavior - -- [x] Keep the existing startup auto-migration model. -- [x] On first startup after upgrade, detect legacy Matrix config/state mismatches and repair them automatically when the repair is deterministic and local. -- [x] Log a concise one-time summary of what was migrated and only show next steps when user action is still required. - -### Doctor and update behavior - -- [x] `openclaw doctor --fix` and update-triggered doctor run the same Matrix migration logic, but with richer user-facing output. -- [x] Doctor shows exactly which Matrix paths/keys were changed and why. -- [x] Doctor validates the installed Matrix plugin source and surfaces manual repair steps for custom path installs. - -### Custom or local-path installs - -- [x] Do not auto-rewrite arbitrary custom plugin paths. -- [x] If the legacy Matrix plugin was installed from a custom path and that path is now stale or missing, warn clearly and print the exact replacement command or path to use. -- [x] If the custom path is valid and already points at the replacement plugin, leave it alone. - -### Unsupported scope - -- [x] No backward compatibility for internal `matrix-js` adopters. -- [x] Do not auto-migrate `channels.matrix-js`, `@openclaw/matrix-js`, `openclaw matrix-js`, or `plugins.entries["matrix-js"]`. - -## Implementation changes - -### 1. Replace identity at the package and plugin layer - -- [x] Overwrite `extensions/matrix` with the Matrix-js implementation instead of renaming user-facing config. -- [x] Delete `extensions/matrix-js` after the port is complete. -- [x] Update `extensions/matrix/package.json`, `extensions/matrix/openclaw.plugin.json`, and `extensions/matrix/index.ts` so the package remains `@openclaw/matrix` but exposes the new feature set. -- [x] Port the Matrix-js CLI and gateway-method registration into `extensions/matrix/index.ts` and register it under `matrix`, not `matrix-js`. -- [x] Replace all internal `openclaw/plugin-sdk/matrix-js` imports with `openclaw/plugin-sdk/matrix`. -- [x] Replace the plugin SDK implementation behind `src/plugin-sdk/matrix.ts` with the Matrix-js helper surface superset, then remove the `matrix-js` plugin-sdk export from `package.json`, `scripts/check-plugin-sdk-exports.mjs`, `scripts/write-plugin-sdk-entry-dts.ts`, and related release/build checks. - -### 2. Preserve legacy `matrix` config compatibility - -- [x] Make the new `matrix` plugin accept the current public legacy `channels.matrix` schema as-is. -- [x] Keep support for top-level single-account `channels.matrix.*`. -- [x] Keep support for `channels.matrix.accounts.*`. -- [x] Keep support for `channels.matrix.defaultAccount`. -- [x] Keep support for the legacy `rooms` alias. -- [x] Keep support for existing DM and group policy keys. -- [x] Keep support for existing bindings that use `match.channel: "matrix"`. -- [x] Preserve SecretRef password inputs used by the legacy plugin. -- [x] Do not require rewriting normal single-account configs into `accounts.default`. -- [x] Add or keep doctor and startup migrations only for keys that are genuinely obsolete or ignored by the new implementation. -- [x] Ensure config help, schema labels, and reference docs all describe `channels.matrix`, never `channels.matrix-js`. - -### 3. Preserve legacy `matrix` state and runtime behavior - -- [x] Keep `credentials/matrix/credentials*.json` as the credential root. -- [x] Keep `matrix/accounts//__//...` as the canonical runtime and crypto root. -- [x] Add explicit migration support in the new plugin for direct upgrades from the oldest legacy flat store: - - [x] `~/.openclaw/matrix/bot-storage.json` - - [x] `~/.openclaw/matrix/crypto/` -- [x] Do not retain `matrix-js`-path migration logic in the shipped plugin. -- [x] Preserve multi-account isolation and default-account behavior exactly on the `matrix` channel. -- [x] Preserve legacy secrets integration by continuing to use the existing `channels.matrix.*` secret collectors and credential surface definitions. -- [x] Keep route/session binding, ACP binding, thread binding, and outbound message routing keyed to `matrix`, with the current new Matrix functionality carried over. - -### 4. Migrate internal Matrix-js-only surfaces to `matrix` - -- [x] Replace every internal channel string and binding string `matrix-js` with `matrix` across ACP binding schemas and runtime. -- [x] Replace every internal channel string and binding string `matrix-js` with `matrix` across thread binding policy and commands. -- [x] Replace every internal channel string and binding string `matrix-js` with `matrix` across auto-reply and session context surfaces. -- [x] Replace every internal channel string and binding string `matrix-js` with `matrix` across agent binding commands and tests. -- [x] Replace all CLI help, onboarding text, runtime warnings, and verification prompts from `matrix-js` to `matrix`. -- [x] Rewrite `docs/channels/matrix.md` to describe the new implementation and new verification, ACP, and thread features. -- [x] Remove `docs/channels/matrix-js.md`. -- [x] Update shared docs that still reference Matrix-js, including `docs/tools/acp-agents.md`, `docs/tools/subagents.md`, `docs/tools/plugin.md`, and `docs/gateway/configuration-reference.md`. -- [x] Leave `docs/zh-CN/**` untouched in this pass. - -### 5. Automatic messaging and failure handling - -- [x] When startup or doctor rewrites Matrix config, emit a short summary such as: - - [x] Matrix plugin upgraded in place - - [x] migrated deprecated Matrix config keys - - [x] migrated legacy Matrix crypto store - - [x] no user action required -- [x] When automatic repair is not safe, emit exact commands, not generic warnings. -- [x] For custom or stale local plugin paths, point users to the concrete replacement command or path. -- [x] Never log secrets or token values in migration output. -- [x] If a legacy state migration fails, continue with clear non-fatal messaging and tell the user what functionality may be degraded until they re-verify. - -## Test plan and acceptance criteria - -### Config compatibility - -- [x] Existing `channels.matrix` single-account config loads unchanged. -- [x] Existing `channels.matrix.accounts.*` config loads unchanged. -- [x] Existing `channels.matrix.defaultAccount` behavior is preserved. -- [x] Existing SecretRef password config continues to validate and resolve. -- [x] Deprecated Matrix-only keys are auto-repaired by startup and doctor with clear change reporting. - -### State compatibility - -- [x] Current canonical `credentials/matrix/*` credentials are reused with no prompt. -- [x] Current canonical `matrix/accounts/*` runtime state is reused with no prompt. -- [x] Oldest flat legacy Matrix crypto and sync store is migrated automatically to account-scoped storage. -- [x] Legacy Matrix encrypted backup material is imported automatically when it can be resolved safely. -- [x] Backed-up Matrix room keys are restored automatically on startup after encrypted-state prep. -- [x] Multi-account state remains isolated after migration. - -### Plugin and install compatibility - -- [x] Existing npm-installed `@openclaw/matrix` updates in place and remains enabled. -- [x] `plugins.installs.matrix` continues to update correctly after the cutover. -- [x] Stale custom path installs are detected and produce exact repair messaging. - -### Public surface - -- [x] `openclaw matrix ...` exposes the verification and account commands that the new Matrix implementation owns. -- [x] `matrix.verify.*` gateway methods work. -- [x] All bindings, ACP, thread, and session flows use `matrix`, not `matrix-js`. -- [x] No shipped docs, help, schema output, or package exports reference `matrix-js`. - -### Regression coverage - -- [x] Startup auto-migration path. -- [x] Doctor `--fix` Matrix migration path. -- [x] Legacy encrypted-state prep and startup restore path. -- [x] Update-triggered doctor path. -- [x] Route bindings and ACP bindings with `match.channel: "matrix"`. -- [x] Thread binding spawn gating and routing on `matrix`. -- [x] Plugin install and update records for `matrix`. - -### Acceptance criteria - -- [x] A current public Matrix user can update and keep using `channels.matrix` without editing config. -- [x] Automatic migration covers every deterministic case. -- [x] Every non-deterministic case produces explicit next steps. -- [x] No public `matrix-js` surface remains in the shipped product. - -## Assumptions and defaults - -- Automatic repair policy: startup plus doctor and update. -- Custom plugin installs: warn and explain; do not mutate arbitrary custom paths automatically. -- No backward compatibility for internal `matrix-js` users or `matrix-js` config, package, CLI, or docs surfaces. -- Canonical external identity after release is `matrix` everywhere. -- The replacement preserves current public `matrix` behavior first, then layers in the newer Matrix features without requiring users to opt into a new namespace. diff --git a/extensions/matrix/scripts/live-basic-send.ts b/extensions/matrix/scripts/live-basic-send.ts deleted file mode 100644 index f0469748c50..00000000000 --- a/extensions/matrix/scripts/live-basic-send.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { sendMatrixMessage } from "../src/matrix/actions.js"; -import { createMatrixClient, resolveMatrixAuth } from "../src/matrix/client.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -async function main() { - const base = resolveLiveHarnessConfig(); - const pluginCfg = installLiveHarnessRuntime(base); - - const auth = await resolveMatrixAuth({ cfg: pluginCfg as never }); - const client = await createMatrixClient({ - homeserver: auth.homeserver, - userId: auth.userId, - accessToken: auth.accessToken, - password: auth.password, - deviceId: auth.deviceId, - encryption: false, - accountId: auth.accountId, - }); - - const targetUserId = process.argv[2]?.trim() || "@user:example.org"; - const stamp = new Date().toISOString(); - - try { - const dmRoomCreate = (await client.doRequest( - "POST", - "/_matrix/client/v3/createRoom", - undefined, - { - is_direct: true, - invite: [targetUserId], - preset: "trusted_private_chat", - name: `OpenClaw DM Test ${stamp}`, - topic: "matrix basic DM messaging test", - }, - )) as { room_id?: string }; - - const dmRoomId = dmRoomCreate.room_id?.trim() ?? ""; - if (!dmRoomId) { - throw new Error("Failed to create DM room"); - } - - const currentDirect = ((await client.getAccountData("m.direct").catch(() => ({}))) ?? - {}) as Record; - const existing = Array.isArray(currentDirect[targetUserId]) ? currentDirect[targetUserId] : []; - await client.setAccountData("m.direct", { - ...currentDirect, - [targetUserId]: [dmRoomId, ...existing.filter((id) => id !== dmRoomId)], - }); - - const dmByUserTarget = await sendMatrixMessage( - targetUserId, - `Matrix basic DM test (user target) ${stamp}`, - { client }, - ); - const dmByRoomTarget = await sendMatrixMessage( - dmRoomId, - `Matrix basic DM test (room target) ${stamp}`, - { client }, - ); - - const roomCreate = (await client.doRequest("POST", "/_matrix/client/v3/createRoom", undefined, { - invite: [targetUserId], - preset: "private_chat", - name: `OpenClaw Room Test ${stamp}`, - topic: "matrix basic room messaging test", - })) as { room_id?: string }; - - const roomId = roomCreate.room_id?.trim() ?? ""; - if (!roomId) { - throw new Error("Failed to create room chat room"); - } - - const roomSend = await sendMatrixMessage(roomId, `Matrix basic room test ${stamp}`, { - client, - }); - - process.stdout.write( - `${JSON.stringify( - { - homeserver: base.homeserver, - senderUserId: base.userId, - targetUserId, - dm: { - roomId: dmRoomId, - userTargetMessageId: dmByUserTarget.messageId, - roomTargetMessageId: dmByRoomTarget.messageId, - }, - room: { - roomId, - messageId: roomSend.messageId, - }, - }, - null, - 2, - )}\n`, - ); - } finally { - client.stop(); - } -} - -main().catch((err) => { - process.stderr.write(`BASIC_SEND_ERROR: ${err instanceof Error ? err.message : String(err)}\n`); - process.exit(1); -}); diff --git a/extensions/matrix/scripts/live-common.ts b/extensions/matrix/scripts/live-common.ts deleted file mode 100644 index 50333ad7d0a..00000000000 --- a/extensions/matrix/scripts/live-common.ts +++ /dev/null @@ -1,145 +0,0 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { setMatrixRuntime } from "../src/runtime.js"; - -type EnvMap = Record; - -function loadEnvFile(filePath: string): EnvMap { - const out: EnvMap = {}; - if (!fs.existsSync(filePath)) { - return out; - } - const raw = fs.readFileSync(filePath, "utf8"); - for (const lineRaw of raw.split(/\r?\n/)) { - const line = lineRaw.trim(); - if (!line || line.startsWith("#")) { - continue; - } - const idx = line.indexOf("="); - if (idx <= 0) { - continue; - } - const key = line.slice(0, idx).trim(); - let value = line.slice(idx + 1).trim(); - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); - } - out[key] = value; - } - return out; -} - -function normalizeHomeserver(raw: string): string { - const trimmed = raw.trim(); - if (!trimmed) { - return ""; - } - return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`; -} - -function chunkText(text: string, limit: number): string[] { - if (!text) { - return []; - } - if (text.length <= limit) { - return [text]; - } - const out: string[] = []; - for (let i = 0; i < text.length; i += limit) { - out.push(text.slice(i, i + limit)); - } - return out; -} - -export type LiveHarnessConfig = { - homeserver: string; - userId: string; - password: string; -}; - -export function resolveLiveHarnessConfig(): LiveHarnessConfig { - const envFromFile = loadEnvFile(path.join(os.homedir(), ".openclaw", ".env")); - const homeserver = normalizeHomeserver( - process.env.MATRIX_HOMESERVER ?? envFromFile.MATRIX_HOMESERVER ?? "", - ); - const userId = process.env.MATRIX_USER_ID ?? envFromFile.MATRIX_USER_ID ?? ""; - const password = process.env.MATRIX_PASSWORD ?? envFromFile.MATRIX_PASSWORD ?? ""; - - if (!homeserver || !userId || !password) { - throw new Error("Missing MATRIX_HOMESERVER / MATRIX_USER_ID / MATRIX_PASSWORD"); - } - - return { - homeserver, - userId, - password, - }; -} - -export function installLiveHarnessRuntime(cfg: LiveHarnessConfig): { - channels: { - matrix: { - homeserver: string; - userId: string; - password: string; - encryption: false; - }; - }; -} { - const pluginCfg = { - channels: { - matrix: { - homeserver: cfg.homeserver, - userId: cfg.userId, - password: cfg.password, - encryption: false as const, - }, - }, - }; - - setMatrixRuntime({ - config: { - loadConfig: () => pluginCfg, - }, - state: { - resolveStateDir: () => path.join(os.homedir(), ".openclaw", "matrix-live-harness-state"), - }, - channel: { - text: { - resolveMarkdownTableMode: () => "off", - convertMarkdownTables: (text: string) => text, - resolveTextChunkLimit: () => 4000, - resolveChunkMode: () => "off", - chunkMarkdownTextWithMode: (text: string, limit: number) => chunkText(text, limit), - }, - }, - media: { - mediaKindFromMime: (mime: string) => { - const value = (mime || "").toLowerCase(); - if (value.startsWith("image/")) { - return "image"; - } - if (value.startsWith("audio/")) { - return "audio"; - } - if (value.startsWith("video/")) { - return "video"; - } - return "document"; - }, - isVoiceCompatibleAudio: () => false, - loadWebMedia: async () => ({ - buffer: Buffer.from("matrix harness media payload\n", "utf8"), - contentType: "text/plain", - fileName: "matrix-harness.txt", - kind: "document" as const, - }), - }, - } as never); - - return pluginCfg; -} diff --git a/extensions/matrix/scripts/live-cross-signing-probe.ts b/extensions/matrix/scripts/live-cross-signing-probe.ts deleted file mode 100644 index 3fbddaf0714..00000000000 --- a/extensions/matrix/scripts/live-cross-signing-probe.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { createMatrixClient, resolveMatrixAuth } from "../src/matrix/client.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -type MatrixCryptoProbe = { - isCrossSigningReady?: () => Promise; - userHasCrossSigningKeys?: (userId?: string, downloadUncached?: boolean) => Promise; - bootstrapCrossSigning?: (opts: { - setupNewCrossSigning?: boolean; - authUploadDeviceSigningKeys?: ( - makeRequest: (authData: Record | null) => Promise, - ) => Promise; - }) => Promise; -}; - -async function main() { - const base = resolveLiveHarnessConfig(); - const cfg = installLiveHarnessRuntime(base); - (cfg.channels["matrix"] as { encryption: boolean }).encryption = true; - - const auth = await resolveMatrixAuth({ cfg: cfg as never }); - const client = await createMatrixClient({ - homeserver: auth.homeserver, - userId: auth.userId, - accessToken: auth.accessToken, - password: auth.password, - deviceId: auth.deviceId, - encryption: true, - accountId: auth.accountId, - }); - const initCrypto = (client as unknown as { initializeCryptoIfNeeded?: () => Promise }) - .initializeCryptoIfNeeded; - if (typeof initCrypto === "function") { - await initCrypto.call(client); - } - - const inner = (client as unknown as { client?: { getCrypto?: () => unknown } }).client; - const crypto = (inner?.getCrypto?.() ?? null) as MatrixCryptoProbe | null; - const userId = auth.userId; - const password = auth.password; - - const out: Record = { - userId, - hasCrypto: Boolean(crypto), - readyBefore: null, - hasKeysBefore: null, - bootstrap: "skipped", - readyAfter: null, - hasKeysAfter: null, - queryHasMaster: null, - queryHasSelfSigning: null, - queryHasUserSigning: null, - }; - - if (!crypto || !crypto.bootstrapCrossSigning) { - process.stdout.write(`${JSON.stringify(out, null, 2)}\n`); - return; - } - - if (typeof crypto.isCrossSigningReady === "function") { - out.readyBefore = await crypto.isCrossSigningReady().catch((err) => `error:${String(err)}`); - } - if (typeof crypto.userHasCrossSigningKeys === "function") { - out.hasKeysBefore = await crypto - .userHasCrossSigningKeys(userId, true) - .catch((err) => `error:${String(err)}`); - } - - const authUploadDeviceSigningKeys = async ( - makeRequest: (authData: Record | null) => Promise, - ): Promise => { - try { - return await makeRequest(null); - } catch { - try { - return await makeRequest({ type: "m.login.dummy" }); - } catch { - if (!password?.trim()) { - throw new Error("Missing password for m.login.password fallback"); - } - return await makeRequest({ - type: "m.login.password", - identifier: { type: "m.id.user", user: userId }, - password, - }); - } - } - }; - - try { - await crypto.bootstrapCrossSigning({ authUploadDeviceSigningKeys }); - out.bootstrap = "ok"; - } catch (err) { - out.bootstrap = "error"; - out.bootstrapError = err instanceof Error ? err.message : String(err); - } - - if (typeof crypto.isCrossSigningReady === "function") { - out.readyAfter = await crypto.isCrossSigningReady().catch((err) => `error:${String(err)}`); - } - if (typeof crypto.userHasCrossSigningKeys === "function") { - out.hasKeysAfter = await crypto - .userHasCrossSigningKeys(userId, true) - .catch((err) => `error:${String(err)}`); - } - - const query = (await client.doRequest("POST", "/_matrix/client/v3/keys/query", undefined, { - device_keys: { [userId]: [] }, - })) as { - master_keys?: Record; - self_signing_keys?: Record; - user_signing_keys?: Record; - }; - - out.queryHasMaster = Boolean(query.master_keys?.[userId]); - out.queryHasSelfSigning = Boolean(query.self_signing_keys?.[userId]); - out.queryHasUserSigning = Boolean(query.user_signing_keys?.[userId]); - - process.stdout.write(`${JSON.stringify(out, null, 2)}\n`); - client.stop(); -} - -main().catch((err) => { - process.stderr.write( - `CROSS_SIGNING_PROBE_ERROR: ${err instanceof Error ? err.message : String(err)}\n`, - ); - process.exit(1); -}); diff --git a/extensions/matrix/scripts/live-e2ee-bootstrap.ts b/extensions/matrix/scripts/live-e2ee-bootstrap.ts deleted file mode 100644 index a24fb3a071a..00000000000 --- a/extensions/matrix/scripts/live-e2ee-bootstrap.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { bootstrapMatrixVerification } from "../src/matrix/actions/verification.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -async function main() { - const recoveryKeyArg = process.argv[2]; - const forceResetCrossSigning = process.argv.includes("--force-reset-cross-signing"); - - const base = resolveLiveHarnessConfig(); - const pluginCfg = installLiveHarnessRuntime(base); - (pluginCfg.channels["matrix"] as { encryption: boolean }).encryption = true; - - const result = await bootstrapMatrixVerification({ - recoveryKey: recoveryKeyArg?.trim() || undefined, - forceResetCrossSigning, - }); - - process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); - if (!result.success) { - process.exitCode = 1; - } -} - -main().catch((err) => { - process.stderr.write( - `E2EE_BOOTSTRAP_ERROR: ${err instanceof Error ? err.message : String(err)}\n`, - ); - process.exit(1); -}); diff --git a/extensions/matrix/scripts/live-e2ee-room-state.ts b/extensions/matrix/scripts/live-e2ee-room-state.ts deleted file mode 100644 index 015febd7cf6..00000000000 --- a/extensions/matrix/scripts/live-e2ee-room-state.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { createMatrixClient, resolveMatrixAuth } from "../src/matrix/client.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -async function main() { - const roomId = process.argv[2]?.trim(); - const eventId = process.argv[3]?.trim(); - - if (!roomId) { - throw new Error( - "Usage: node --import tsx extensions/matrix/scripts/live-e2ee-room-state.ts [eventId]", - ); - } - - const base = resolveLiveHarnessConfig(); - const pluginCfg = installLiveHarnessRuntime(base); - (pluginCfg.channels["matrix"] as { encryption: boolean }).encryption = true; - - const auth = await resolveMatrixAuth({ cfg: pluginCfg as never }); - const client = await createMatrixClient({ - homeserver: auth.homeserver, - userId: auth.userId, - accessToken: auth.accessToken, - password: auth.password, - deviceId: auth.deviceId, - encryption: false, - accountId: auth.accountId, - }); - - try { - const encryptionState = (await client.doRequest( - "GET", - `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/state/m.room.encryption/`, - )) as { algorithm?: string; rotation_period_ms?: number; rotation_period_msgs?: number }; - - let eventType: string | null = null; - if (eventId) { - const event = (await client.doRequest( - "GET", - `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/event/${encodeURIComponent(eventId)}`, - )) as { type?: string }; - eventType = event.type ?? null; - } - - process.stdout.write( - `${JSON.stringify( - { - roomId, - encryptionState, - eventId: eventId ?? null, - eventType, - }, - null, - 2, - )}\n`, - ); - } finally { - client.stop(); - } -} - -main().catch((err) => { - process.stderr.write( - `E2EE_ROOM_STATE_ERROR: ${err instanceof Error ? err.message : String(err)}\n`, - ); - process.exit(1); -}); diff --git a/extensions/matrix/scripts/live-e2ee-send-room.ts b/extensions/matrix/scripts/live-e2ee-send-room.ts deleted file mode 100644 index 85e87e7a0d3..00000000000 --- a/extensions/matrix/scripts/live-e2ee-send-room.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { sendMatrixMessage } from "../src/matrix/actions.js"; -import { createMatrixClient, resolveMatrixAuth } from "../src/matrix/client.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -async function delay(ms: number): Promise { - await new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function main() { - const roomId = process.argv[2]?.trim(); - const useFullBootstrap = process.argv.includes("--full-bootstrap"); - const startupTimeoutMs = 45_000; - const settleMsRaw = Number.parseInt(process.argv[3] ?? "4000", 10); - const settleMs = Number.isFinite(settleMsRaw) && settleMsRaw >= 0 ? settleMsRaw : 4000; - - if (!roomId) { - throw new Error( - "Usage: node --import tsx extensions/matrix/scripts/live-e2ee-send-room.ts [settleMs] [--full-bootstrap]", - ); - } - - const base = resolveLiveHarnessConfig(); - const pluginCfg = installLiveHarnessRuntime(base); - (pluginCfg.channels["matrix"] as { encryption: boolean }).encryption = true; - - const auth = await resolveMatrixAuth({ cfg: pluginCfg as never }); - const client = await createMatrixClient({ - homeserver: auth.homeserver, - userId: auth.userId, - accessToken: auth.accessToken, - password: auth.password, - deviceId: auth.deviceId, - encryption: true, - accountId: auth.accountId, - }); - - const stamp = new Date().toISOString(); - - try { - if (!useFullBootstrap) { - const bootstrapper = ( - client as unknown as { cryptoBootstrapper?: { bootstrap?: () => Promise } } - ).cryptoBootstrapper; - if (bootstrapper?.bootstrap) { - bootstrapper.bootstrap = async () => {}; - } - } - - await Promise.race([ - client.start(), - new Promise((_, reject) => { - setTimeout(() => { - reject( - new Error( - `Matrix client start timed out after ${startupTimeoutMs}ms (fullBootstrap=${useFullBootstrap})`, - ), - ); - }, startupTimeoutMs); - }), - ]); - - if (settleMs > 0) { - await delay(settleMs); - } - - const sent = await sendMatrixMessage( - roomId, - `Matrix E2EE existing-room test ${stamp} (settleMs=${settleMs})`, - { client }, - ); - - const event = (await client.doRequest( - "GET", - `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/event/${encodeURIComponent(sent.messageId)}`, - )) as { type?: string }; - - process.stdout.write( - `${JSON.stringify( - { - roomId, - messageId: sent.messageId, - storedEventType: event.type ?? null, - fullBootstrap: useFullBootstrap, - settleMs, - }, - null, - 2, - )}\n`, - ); - } finally { - client.stop(); - } -} - -main().catch((err) => { - process.stderr.write( - `E2EE_SEND_ROOM_ERROR: ${err instanceof Error ? err.message : String(err)}\n`, - ); - process.exit(1); -}); diff --git a/extensions/matrix/scripts/live-e2ee-send.ts b/extensions/matrix/scripts/live-e2ee-send.ts deleted file mode 100644 index 6d6977622c9..00000000000 --- a/extensions/matrix/scripts/live-e2ee-send.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { sendMatrixMessage } from "../src/matrix/actions.js"; -import { createMatrixClient, resolveMatrixAuth } from "../src/matrix/client.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -const MEGOLM_ALG = "m.megolm.v1.aes-sha2"; - -type MatrixEventLike = { - type?: string; -}; - -async function main() { - const targetUserId = process.argv[2]?.trim() || "@user:example.org"; - const useFullBootstrap = process.argv.includes("--full-bootstrap"); - const startupTimeoutMs = 45_000; - const base = resolveLiveHarnessConfig(); - const pluginCfg = installLiveHarnessRuntime(base); - - // Enable encryption for this run only. - (pluginCfg.channels["matrix"] as { encryption: boolean }).encryption = true; - - const auth = await resolveMatrixAuth({ cfg: pluginCfg as never }); - const client = await createMatrixClient({ - homeserver: auth.homeserver, - userId: auth.userId, - accessToken: auth.accessToken, - password: auth.password, - deviceId: auth.deviceId, - encryption: true, - accountId: auth.accountId, - }); - - const stamp = new Date().toISOString(); - - try { - if (!useFullBootstrap) { - const bootstrapper = ( - client as unknown as { cryptoBootstrapper?: { bootstrap?: () => Promise } } - ).cryptoBootstrapper; - if (bootstrapper?.bootstrap) { - bootstrapper.bootstrap = async () => {}; - } - } - - await Promise.race([ - client.start(), - new Promise((_, reject) => { - setTimeout(() => { - reject( - new Error( - `Matrix client start timed out after ${startupTimeoutMs}ms (fullBootstrap=${useFullBootstrap})`, - ), - ); - }, startupTimeoutMs); - }), - ]); - - const dmRoomCreate = (await client.doRequest( - "POST", - "/_matrix/client/v3/createRoom", - undefined, - { - is_direct: true, - invite: [targetUserId], - preset: "trusted_private_chat", - name: `OpenClaw E2EE DM ${stamp}`, - topic: "matrix E2EE DM test", - initial_state: [ - { - type: "m.room.encryption", - state_key: "", - content: { - algorithm: MEGOLM_ALG, - }, - }, - ], - }, - )) as { room_id?: string }; - - const dmRoomId = dmRoomCreate.room_id?.trim() ?? ""; - if (!dmRoomId) { - throw new Error("Failed to create encrypted DM room"); - } - - const currentDirect = ((await client.getAccountData("m.direct").catch(() => ({}))) ?? - {}) as Record; - const existing = Array.isArray(currentDirect[targetUserId]) ? currentDirect[targetUserId] : []; - await client.setAccountData("m.direct", { - ...currentDirect, - [targetUserId]: [dmRoomId, ...existing.filter((id) => id !== dmRoomId)], - }); - - const dmSend = await sendMatrixMessage( - dmRoomId, - `Matrix E2EE DM test ${stamp}\nPlease reply here so I can validate decrypt/read.`, - { - client, - }, - ); - - const roomCreate = (await client.doRequest("POST", "/_matrix/client/v3/createRoom", undefined, { - invite: [targetUserId], - preset: "private_chat", - name: `OpenClaw E2EE Room ${stamp}`, - topic: "matrix E2EE room test", - initial_state: [ - { - type: "m.room.encryption", - state_key: "", - content: { - algorithm: MEGOLM_ALG, - }, - }, - ], - })) as { room_id?: string }; - - const roomId = roomCreate.room_id?.trim() ?? ""; - if (!roomId) { - throw new Error("Failed to create encrypted room chat"); - } - - const roomSend = await sendMatrixMessage( - roomId, - `Matrix E2EE room test ${stamp}\nPlease reply here too.`, - { - client, - }, - ); - - const dmRaw = (await client.doRequest( - "GET", - `/_matrix/client/v3/rooms/${encodeURIComponent(dmRoomId)}/event/${encodeURIComponent(dmSend.messageId)}`, - )) as MatrixEventLike; - - const roomRaw = (await client.doRequest( - "GET", - `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/event/${encodeURIComponent(roomSend.messageId)}`, - )) as MatrixEventLike; - - process.stdout.write( - `${JSON.stringify( - { - homeserver: base.homeserver, - senderUserId: base.userId, - targetUserId, - encryptionAlgorithm: MEGOLM_ALG, - fullBootstrap: useFullBootstrap, - dm: { - roomId: dmRoomId, - messageId: dmSend.messageId, - storedEventType: dmRaw.type ?? null, - }, - room: { - roomId, - messageId: roomSend.messageId, - storedEventType: roomRaw.type ?? null, - }, - }, - null, - 2, - )}\n`, - ); - } finally { - client.stop(); - } -} - -main().catch((err) => { - process.stderr.write(`E2EE_SEND_ERROR: ${err instanceof Error ? err.message : String(err)}\n`); - process.exit(1); -}); diff --git a/extensions/matrix/scripts/live-e2ee-status.ts b/extensions/matrix/scripts/live-e2ee-status.ts deleted file mode 100644 index 520d001bc84..00000000000 --- a/extensions/matrix/scripts/live-e2ee-status.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - getMatrixEncryptionStatus, - getMatrixVerificationStatus, - verifyMatrixRecoveryKey, -} from "../src/matrix/actions.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -async function main() { - const includeRecoveryKey = process.argv.includes("--include-recovery-key"); - const verifyStoredRecoveryKey = process.argv.includes("--verify-stored-recovery-key"); - - const base = resolveLiveHarnessConfig(); - const pluginCfg = installLiveHarnessRuntime(base); - (pluginCfg.channels["matrix"] as { encryption: boolean }).encryption = true; - - const verification = await getMatrixVerificationStatus({ - includeRecoveryKey, - }); - const encryption = await getMatrixEncryptionStatus({ - includeRecoveryKey, - }); - - let recoveryVerificationResult: unknown = null; - if (verifyStoredRecoveryKey) { - const key = - verification && typeof verification === "object" && "recoveryKey" in verification - ? (verification as { recoveryKey?: string | null }).recoveryKey - : null; - if (key?.trim()) { - recoveryVerificationResult = await verifyMatrixRecoveryKey(key); - } else { - recoveryVerificationResult = { - success: false, - error: "No stored recovery key returned (use --include-recovery-key)", - }; - } - } - - process.stdout.write( - `${JSON.stringify( - { - homeserver: base.homeserver, - userId: base.userId, - verification, - encryption, - recoveryVerificationResult, - }, - null, - 2, - )}\n`, - ); -} - -main().catch((err) => { - process.stderr.write(`E2EE_STATUS_ERROR: ${err instanceof Error ? err.message : String(err)}\n`); - process.exit(1); -}); diff --git a/extensions/matrix/scripts/live-e2ee-wait-reply.ts b/extensions/matrix/scripts/live-e2ee-wait-reply.ts deleted file mode 100644 index cf415bd1ede..00000000000 --- a/extensions/matrix/scripts/live-e2ee-wait-reply.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { createMatrixClient, resolveMatrixAuth } from "../src/matrix/client.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -type MatrixRawEvent = { - event_id?: string; - type?: string; - sender?: string; - room_id?: string; - origin_server_ts?: number; - content?: { - body?: string; - msgtype?: string; - }; -}; - -async function main() { - const roomId = process.argv[2]?.trim(); - const targetUserId = process.argv[3]?.trim() || "@user:example.org"; - const timeoutSecRaw = Number.parseInt(process.argv[4] ?? "120", 10); - const timeoutMs = - (Number.isFinite(timeoutSecRaw) && timeoutSecRaw > 0 ? timeoutSecRaw : 120) * 1000; - const useFullBootstrap = process.argv.includes("--full-bootstrap"); - const startupTimeoutMs = 45_000; - - if (!roomId) { - throw new Error( - "Usage: node --import tsx extensions/matrix/scripts/live-e2ee-wait-reply.ts [targetUserId] [timeoutSec] [--full-bootstrap]", - ); - } - - const base = resolveLiveHarnessConfig(); - const pluginCfg = installLiveHarnessRuntime(base); - (pluginCfg.channels["matrix"] as { encryption: boolean }).encryption = true; - - const auth = await resolveMatrixAuth({ cfg: pluginCfg as never }); - const client = await createMatrixClient({ - homeserver: auth.homeserver, - userId: auth.userId, - accessToken: auth.accessToken, - password: auth.password, - deviceId: auth.deviceId, - encryption: true, - accountId: auth.accountId, - }); - - try { - if (!useFullBootstrap) { - const bootstrapper = ( - client as unknown as { cryptoBootstrapper?: { bootstrap?: () => Promise } } - ).cryptoBootstrapper; - if (bootstrapper?.bootstrap) { - bootstrapper.bootstrap = async () => {}; - } - } - - await Promise.race([ - client.start(), - new Promise((_, reject) => { - setTimeout(() => { - reject( - new Error( - `Matrix client start timed out after ${startupTimeoutMs}ms (fullBootstrap=${useFullBootstrap})`, - ), - ); - }, startupTimeoutMs); - }), - ]); - - const found = await new Promise((resolve) => { - const timer = setTimeout(() => { - resolve(null); - }, timeoutMs); - - client.on("room.message", (eventRoomId, event) => { - const rid = String(eventRoomId || ""); - const raw = event as MatrixRawEvent; - if (rid !== roomId) { - return; - } - if ((raw.sender ?? "").trim() !== targetUserId) { - return; - } - if ((raw.type ?? "").trim() !== "m.room.message") { - return; - } - clearTimeout(timer); - resolve(raw); - }); - }); - - process.stdout.write( - `${JSON.stringify( - { - roomId, - targetUserId, - timeoutMs, - found: Boolean(found), - message: found - ? { - eventId: found.event_id ?? null, - type: found.type ?? null, - sender: found.sender ?? null, - timestamp: found.origin_server_ts ?? null, - text: found.content?.body ?? null, - msgtype: found.content?.msgtype ?? null, - } - : null, - }, - null, - 2, - )}\n`, - ); - } finally { - client.stop(); - } -} - -main().catch((err) => { - process.stderr.write( - `E2EE_WAIT_REPLY_ERROR: ${err instanceof Error ? err.message : String(err)}\n`, - ); - process.exit(1); -}); diff --git a/extensions/matrix/scripts/live-read-room.ts b/extensions/matrix/scripts/live-read-room.ts deleted file mode 100644 index 2bf3df85d09..00000000000 --- a/extensions/matrix/scripts/live-read-room.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { readMatrixMessages } from "../src/matrix/actions.js"; -import { createMatrixClient, resolveMatrixAuth } from "../src/matrix/client.js"; -import { installLiveHarnessRuntime, resolveLiveHarnessConfig } from "./live-common.js"; - -async function main() { - const roomId = process.argv[2]?.trim(); - if (!roomId) { - throw new Error("Usage: bun extensions/matrix/scripts/live-read-room.ts [limit]"); - } - - const requestedLimit = Number.parseInt(process.argv[3] ?? "30", 10); - const limit = Number.isFinite(requestedLimit) && requestedLimit > 0 ? requestedLimit : 30; - - const base = resolveLiveHarnessConfig(); - const pluginCfg = installLiveHarnessRuntime(base); - const auth = await resolveMatrixAuth({ cfg: pluginCfg as never }); - const client = await createMatrixClient({ - homeserver: auth.homeserver, - userId: auth.userId, - accessToken: auth.accessToken, - password: auth.password, - deviceId: auth.deviceId, - encryption: false, - accountId: auth.accountId, - }); - - try { - const result = await readMatrixMessages(roomId, { client, limit }); - const compact = result.messages.map((msg) => ({ - id: msg.eventId, - sender: msg.sender, - ts: msg.timestamp, - text: msg.body ?? "", - })); - - process.stdout.write( - `${JSON.stringify( - { - roomId, - count: compact.length, - messages: compact, - nextBatch: result.nextBatch ?? null, - prevBatch: result.prevBatch ?? null, - }, - null, - 2, - )}\n`, - ); - } finally { - client.stop(); - } -} - -main().catch((err) => { - process.stderr.write(`READ_ROOM_ERROR: ${err instanceof Error ? err.message : String(err)}\n`); - process.exit(1); -}); diff --git a/extensions/matrix/src/channel.directory.test.ts b/extensions/matrix/src/channel.directory.test.ts index 6af9283d77f..d8b1190e362 100644 --- a/extensions/matrix/src/channel.directory.test.ts +++ b/extensions/matrix/src/channel.directory.test.ts @@ -476,7 +476,7 @@ describe("matrix directory", () => { input: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", - password: "new-password", + password: "new-password", // pragma: allowlist secret }, }) as CoreConfig; @@ -492,7 +492,7 @@ describe("matrix directory", () => { default: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", - password: "old-password", + password: "old-password", // pragma: allowlist secret }, }, }, diff --git a/extensions/matrix/src/cli.test.ts b/extensions/matrix/src/cli.test.ts index a1bb7c45b55..bbcf2024f96 100644 --- a/extensions/matrix/src/cli.test.ts +++ b/extensions/matrix/src/cli.test.ts @@ -308,7 +308,7 @@ describe("matrix CLI verification commands", () => { input: expect.objectContaining({ homeserver: "https://matrix.example.org", userId: "@ops:example.org", - password: "secret", + password: "secret", // pragma: allowlist secret }), }), ); diff --git a/extensions/matrix/src/matrix/client.test.ts b/extensions/matrix/src/matrix/client.test.ts index 84bfc9f7e88..5ddb1d371b6 100644 --- a/extensions/matrix/src/matrix/client.test.ts +++ b/extensions/matrix/src/matrix/client.test.ts @@ -110,7 +110,7 @@ describe("resolveMatrixAuth", () => { matrix: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", - password: "secret", + password: "secret", // pragma: allowlist secret encryption: true, }, }, @@ -158,7 +158,7 @@ describe("resolveMatrixAuth", () => { matrix: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", - password: "secret", + password: "secret", // pragma: allowlist secret }, }, } as CoreConfig; @@ -196,7 +196,7 @@ describe("resolveMatrixAuth", () => { matrix: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", - password: "secret", + password: "secret", // pragma: allowlist secret }, }, } as CoreConfig; diff --git a/extensions/matrix/src/matrix/client/config.ts b/extensions/matrix/src/matrix/client/config.ts index 6f2f548c820..b95e2432e1a 100644 --- a/extensions/matrix/src/matrix/client/config.ts +++ b/extensions/matrix/src/matrix/client/config.ts @@ -108,7 +108,7 @@ export function getMatrixScopedEnvVarNames(accountId: string): { homeserver: `MATRIX_${token}_HOMESERVER`, userId: `MATRIX_${token}_USER_ID`, accessToken: `MATRIX_${token}_ACCESS_TOKEN`, - password: `MATRIX_${token}_PASSWORD`, + password: `MATRIX_${token}_PASSWORD`, // pragma: allowlist secret deviceId: `MATRIX_${token}_DEVICE_ID`, deviceName: `MATRIX_${token}_DEVICE_NAME`, }; diff --git a/extensions/matrix/src/matrix/client/shared.test.ts b/extensions/matrix/src/matrix/client/shared.test.ts index bebed5f0cf7..43919cb712b 100644 --- a/extensions/matrix/src/matrix/client/shared.test.ts +++ b/extensions/matrix/src/matrix/client/shared.test.ts @@ -26,7 +26,7 @@ function authFor(accountId: string): MatrixAuth { homeserver: "https://matrix.example.org", userId: `@${accountId}:example.org`, accessToken: `token-${accountId}`, - password: "secret", + password: "secret", // pragma: allowlist secret deviceId: `${accountId.toUpperCase()}-DEVICE`, deviceName: `${accountId} device`, initialSyncLimit: undefined, diff --git a/extensions/matrix/src/matrix/config-update.test.ts b/extensions/matrix/src/matrix/config-update.test.ts index 92a7ac344f1..f2a6e1941b3 100644 --- a/extensions/matrix/src/matrix/config-update.test.ts +++ b/extensions/matrix/src/matrix/config-update.test.ts @@ -31,8 +31,8 @@ describe("updateMatrixAccountConfig", () => { default: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", - accessToken: "old-token", - password: "old-password", + accessToken: "old-token", // pragma: allowlist secret + password: "old-password", // pragma: allowlist secret encryption: true, }, }, diff --git a/extensions/matrix/src/matrix/sdk.test.ts b/extensions/matrix/src/matrix/sdk.test.ts index b224887168f..b327e3d2241 100644 --- a/extensions/matrix/src/matrix/sdk.test.ts +++ b/extensions/matrix/src/matrix/sdk.test.ts @@ -813,7 +813,7 @@ describe("MatrixClient crypto bootstrapping", () => { matrixJsClient.getCrypto = vi.fn(() => ({ on: vi.fn() })); const client = new MatrixClient("https://matrix.example.org", "token", undefined, undefined, { encryption: true, - password: "secret-password", + password: "secret-password", // pragma: allowlist secret }); const bootstrapSpy = vi .fn() @@ -846,7 +846,7 @@ describe("MatrixClient crypto bootstrapping", () => { matrixJsClient.getCrypto = vi.fn(() => ({ on: vi.fn() })); const client = new MatrixClient("https://matrix.example.org", "token", undefined, undefined, { encryption: true, - password: "secret-password", + password: "secret-password", // pragma: allowlist secret }); const bootstrapSpy = vi.fn().mockResolvedValue({ crossSigningReady: false, diff --git a/extensions/matrix/src/matrix/sdk.ts b/extensions/matrix/src/matrix/sdk.ts index 925811354fc..0c0f3223b00 100644 --- a/extensions/matrix/src/matrix/sdk.ts +++ b/extensions/matrix/src/matrix/sdk.ts @@ -679,10 +679,13 @@ export class MatrixClient { let keyLoadAttempted = false; let keyLoadError: string | null = null; if (serverVersion && decryptionKeyCached === false) { - if (typeof crypto.loadSessionBackupPrivateKeyFromSecretStorage === "function") { + if ( + typeof crypto.loadSessionBackupPrivateKeyFromSecretStorage === + "function" /* pragma: allowlist secret */ + ) { keyLoadAttempted = true; try { - await crypto.loadSessionBackupPrivateKeyFromSecretStorage(); + await crypto.loadSessionBackupPrivateKeyFromSecretStorage(); // pragma: allowlist secret } catch (err) { keyLoadError = err instanceof Error ? err.message : String(err); } @@ -777,8 +780,9 @@ export class MatrixClient { } let defaultKeyId: string | null | undefined = undefined; - if (typeof crypto.getSecretStorageStatus === "function") { - const status = await crypto.getSecretStorageStatus().catch(() => null); + const canReadSecretStorageStatus = typeof crypto.getSecretStorageStatus === "function"; // pragma: allowlist secret + if (canReadSecretStorageStatus) { + const status = await crypto.getSecretStorageStatus().catch(() => null); // pragma: allowlist secret defaultKeyId = status?.defaultKeyId; } @@ -844,8 +848,9 @@ export class MatrixClient { const rawRecoveryKey = params.recoveryKey?.trim(); if (rawRecoveryKey) { let defaultKeyId: string | null | undefined = undefined; - if (typeof crypto.getSecretStorageStatus === "function") { - const status = await crypto.getSecretStorageStatus().catch(() => null); + const canReadSecretStorageStatus = typeof crypto.getSecretStorageStatus === "function"; // pragma: allowlist secret + if (canReadSecretStorageStatus) { + const status = await crypto.getSecretStorageStatus().catch(() => null); // pragma: allowlist secret defaultKeyId = status?.defaultKeyId; } this.recoveryKeyStore.storeEncodedRecoveryKey({ @@ -856,12 +861,15 @@ export class MatrixClient { let activeVersion = await this.resolveActiveRoomKeyBackupVersion(crypto); if (!activeVersion) { - if (typeof crypto.loadSessionBackupPrivateKeyFromSecretStorage !== "function") { + if ( + typeof crypto.loadSessionBackupPrivateKeyFromSecretStorage !== + "function" /* pragma: allowlist secret */ + ) { return await fail( "Matrix crypto backend cannot load backup keys from secret storage. Verify this device with 'openclaw matrix verify device ' first.", ); } - await crypto.loadSessionBackupPrivateKeyFromSecretStorage(); + await crypto.loadSessionBackupPrivateKeyFromSecretStorage(); // pragma: allowlist secret loadedFromSecretStorage = true; activeVersion = await this.resolveActiveRoomKeyBackupVersion(crypto); } @@ -960,8 +968,9 @@ export class MatrixClient { const rawRecoveryKey = params?.recoveryKey?.trim(); if (rawRecoveryKey) { let defaultKeyId: string | null | undefined = undefined; - if (typeof crypto.getSecretStorageStatus === "function") { - const status = await crypto.getSecretStorageStatus().catch(() => null); + const canReadSecretStorageStatus = typeof crypto.getSecretStorageStatus === "function"; // pragma: allowlist secret + if (canReadSecretStorageStatus) { + const status = await crypto.getSecretStorageStatus().catch(() => null); // pragma: allowlist secret defaultKeyId = status?.defaultKeyId; } this.recoveryKeyStore.storeEncodedRecoveryKey({ @@ -1072,10 +1081,11 @@ export class MatrixClient { private async resolveCachedRoomKeyBackupDecryptionKey( crypto: MatrixCryptoBootstrapApi, ): Promise { - if (typeof crypto.getSessionBackupPrivateKey !== "function") { + const canGetSessionBackupPrivateKey = typeof crypto.getSessionBackupPrivateKey === "function"; // pragma: allowlist secret + if (!canGetSessionBackupPrivateKey) { return null; } - const key = await crypto.getSessionBackupPrivateKey().catch(() => null); + const key = await crypto.getSessionBackupPrivateKey().catch(() => null); // pragma: allowlist secret return key ? key.length > 0 : false; } diff --git a/extensions/matrix/src/matrix/sdk/crypto-bootstrap.test.ts b/extensions/matrix/src/matrix/sdk/crypto-bootstrap.test.ts index c91915d0974..54a9615b19a 100644 --- a/extensions/matrix/src/matrix/sdk/crypto-bootstrap.test.ts +++ b/extensions/matrix/src/matrix/sdk/crypto-bootstrap.test.ts @@ -279,7 +279,7 @@ describe("MatrixCryptoBootstrapper", () => { { type: "m.login.password", identifier: { type: "m.id.user", user: "@bot:example.org" }, - password: "super-secret-password", + password: "super-secret-password", // pragma: allowlist secret }, ]); }); diff --git a/extensions/matrix/src/matrix/sdk/recovery-key-store.test.ts b/extensions/matrix/src/matrix/sdk/recovery-key-store.test.ts index d7c58d00f7d..88a7d6501bc 100644 --- a/extensions/matrix/src/matrix/sdk/recovery-key-store.test.ts +++ b/extensions/matrix/src/matrix/sdk/recovery-key-store.test.ts @@ -71,7 +71,7 @@ describe("MatrixRecoveryKeyStore", () => { keyId: "GENERATED", keyInfo: { name: "generated" }, privateKey: new Uint8Array([5, 6, 7, 8]), - encodedPrivateKey: "encoded-generated-key", + encodedPrivateKey: "encoded-generated-key", // pragma: allowlist secret }; const createRecoveryKeyFromPassphrase = vi.fn(async () => generated); const bootstrapSecretStorage = vi.fn( @@ -98,7 +98,7 @@ describe("MatrixRecoveryKeyStore", () => { ); expect(store.getRecoveryKeySummary()).toMatchObject({ keyId: "GENERATED", - encodedPrivateKey: "encoded-generated-key", + encodedPrivateKey: "encoded-generated-key", // pragma: allowlist secret }); }); @@ -144,7 +144,7 @@ describe("MatrixRecoveryKeyStore", () => { keyId: "RECOVERED", keyInfo: { name: "recovered" }, privateKey: new Uint8Array([1, 1, 2, 3]), - encodedPrivateKey: "encoded-recovered-key", + encodedPrivateKey: "encoded-recovered-key", // pragma: allowlist secret }; const createRecoveryKeyFromPassphrase = vi.fn(async () => generated); const bootstrapSecretStorage = vi.fn( @@ -171,7 +171,7 @@ describe("MatrixRecoveryKeyStore", () => { ); expect(store.getRecoveryKeySummary()).toMatchObject({ keyId: "RECOVERED", - encodedPrivateKey: "encoded-recovered-key", + encodedPrivateKey: "encoded-recovered-key", // pragma: allowlist secret }); }); @@ -182,7 +182,7 @@ describe("MatrixRecoveryKeyStore", () => { keyId: "REPAIRED", keyInfo: { name: "repaired" }, privateKey: new Uint8Array([7, 7, 8, 9]), - encodedPrivateKey: "encoded-repaired-key", + encodedPrivateKey: "encoded-repaired-key", // pragma: allowlist secret }; const createRecoveryKeyFromPassphrase = vi.fn(async () => generated); const bootstrapSecretStorage = vi.fn( @@ -223,7 +223,7 @@ describe("MatrixRecoveryKeyStore", () => { ); expect(store.getRecoveryKeySummary()).toMatchObject({ keyId: "REPAIRED", - encodedPrivateKey: "encoded-repaired-key", + encodedPrivateKey: "encoded-repaired-key", // pragma: allowlist secret }); }); diff --git a/extensions/matrix/src/matrix/sdk/recovery-key-store.ts b/extensions/matrix/src/matrix/sdk/recovery-key-store.ts index 580296ffab1..5e85ae1006b 100644 --- a/extensions/matrix/src/matrix/sdk/recovery-key-store.ts +++ b/extensions/matrix/src/matrix/sdk/recovery-key-store.ts @@ -135,7 +135,8 @@ export class MatrixRecoveryKeyStore { } = {}, ): Promise { let status: MatrixSecretStorageStatus | null = null; - if (typeof crypto.getSecretStorageStatus === "function") { + const canReadSecretStorageStatus = typeof crypto.getSecretStorageStatus === "function"; // pragma: allowlist secret + if (canReadSecretStorageStatus) { try { status = await crypto.getSecretStorageStatus(); } catch (err) { @@ -268,7 +269,7 @@ export class MatrixRecoveryKeyStore { if ( parsed.version !== 1 || typeof parsed.createdAt !== "string" || - typeof parsed.privateKeyBase64 !== "string" || + typeof parsed.privateKeyBase64 !== "string" || // pragma: allowlist secret !parsed.privateKeyBase64.trim() ) { return null; diff --git a/extensions/matrix/src/onboarding.test.ts b/extensions/matrix/src/onboarding.test.ts index d956558c72c..b2d585a2e67 100644 --- a/extensions/matrix/src/onboarding.test.ts +++ b/extensions/matrix/src/onboarding.test.ts @@ -44,7 +44,7 @@ describe("matrix onboarding", () => { process.env.MATRIX_HOMESERVER = "https://matrix.env.example.org"; process.env.MATRIX_USER_ID = "@env:example.org"; - process.env.MATRIX_PASSWORD = "env-password"; + process.env.MATRIX_PASSWORD = "env-password"; // pragma: allowlist secret process.env.MATRIX_ACCESS_TOKEN = ""; process.env.MATRIX_OPS_HOMESERVER = "https://matrix.ops.env.example.org"; process.env.MATRIX_OPS_ACCESS_TOKEN = "ops-env-token"; diff --git a/src/infra/matrix-legacy-state.test.ts b/src/infra/matrix-legacy-state.test.ts index 22bdf76c7cd..096f2752a57 100644 --- a/src/infra/matrix-legacy-state.test.ts +++ b/src/infra/matrix-legacy-state.test.ts @@ -65,7 +65,7 @@ describe("matrix legacy state migration", () => { matrix: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", - password: "secret", + password: "secret", // pragma: allowlist secret }, }, };