mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-29 10:50:58 +00:00
refactor: lazy-load matrix setup bootstrap surfaces
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { matrixPlugin } from "./src/channel.js";
|
||||
import { registerMatrixCli } from "./src/cli.js";
|
||||
import { setMatrixRuntime } from "./src/runtime.js";
|
||||
|
||||
export { matrixPlugin } from "./src/channel.js";
|
||||
@@ -41,7 +40,8 @@ export default defineChannelPluginEntry({
|
||||
});
|
||||
|
||||
api.registerCli(
|
||||
({ program }) => {
|
||||
async ({ program }) => {
|
||||
const { registerMatrixCli } = await import("./src/cli.js");
|
||||
registerMatrixCli({ program });
|
||||
},
|
||||
{ commands: ["matrix"] },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
import {
|
||||
adaptScopedAccountAccessor,
|
||||
createScopedChannelConfigAdapter,
|
||||
@@ -12,8 +13,13 @@ import {
|
||||
createAllowlistProviderOpenWarningCollector,
|
||||
projectAccountConfigWarningCollector,
|
||||
} from "openclaw/plugin-sdk/channel-policy";
|
||||
import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
|
||||
import { createScopedAccountReplyToModeResolver } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
createChatChannelPlugin,
|
||||
type ChannelPlugin,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
createChannelDirectoryAdapter,
|
||||
createResolvedDirectoryEntriesLister,
|
||||
@@ -23,6 +29,8 @@ import { buildTrafficStatusSummary } from "openclaw/plugin-sdk/extension-shared"
|
||||
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import {
|
||||
buildProbeChannelStatusSummary,
|
||||
collectStatusIssuesFromLastError,
|
||||
createComputedAccountStatusAdapter,
|
||||
createDefaultChannelRuntimeState,
|
||||
} from "openclaw/plugin-sdk/status-helpers";
|
||||
@@ -46,15 +54,6 @@ import {
|
||||
resolveMatrixDirectUserId,
|
||||
resolveMatrixTargetIdentity,
|
||||
} from "./matrix/target-ids.js";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
buildProbeChannelStatusSummary,
|
||||
chunkTextForOutbound,
|
||||
collectStatusIssuesFromLastError,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
type ChannelPlugin,
|
||||
} from "./runtime-api.js";
|
||||
import { getMatrixRuntime } from "./runtime.js";
|
||||
import { resolveMatrixOutboundSessionRoute } from "./session-route.js";
|
||||
import { matrixSetupAdapter } from "./setup-core.js";
|
||||
@@ -63,6 +62,22 @@ import type { CoreConfig } from "./types.js";
|
||||
// Mutex for serializing account startup (workaround for concurrent dynamic import race condition)
|
||||
let matrixStartupLock: Promise<void> = Promise.resolve();
|
||||
|
||||
function chunkTextForOutbound(text: string, limit: number): string[] {
|
||||
const chunks: string[] = [];
|
||||
let remaining = text;
|
||||
while (remaining.length > limit) {
|
||||
const window = remaining.slice(0, limit);
|
||||
const splitAt = Math.max(window.lastIndexOf("\n"), window.lastIndexOf(" "));
|
||||
const breakAt = splitAt > 0 ? splitAt : limit;
|
||||
chunks.push(remaining.slice(0, breakAt).trimEnd());
|
||||
remaining = remaining.slice(breakAt).trimStart();
|
||||
}
|
||||
if (remaining.length > 0 || text.length === 0) {
|
||||
chunks.push(remaining);
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
const loadMatrixChannelRuntime = createLazyRuntimeNamedExport(
|
||||
() => import("./channel.runtime.js"),
|
||||
"matrixChannelRuntime",
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
import { applyMatrixProfileUpdate, type MatrixProfileUpdateResult } from "./profile-update.js";
|
||||
import { formatZonedTimestamp, normalizeAccountId, type ChannelSetupInput } from "./runtime-api.js";
|
||||
import { getMatrixRuntime } from "./runtime.js";
|
||||
import { maybeBootstrapNewEncryptedMatrixAccount } from "./setup-bootstrap.js";
|
||||
import { matrixSetupAdapter } from "./setup-core.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
@@ -215,6 +214,7 @@ async function addMatrixAccount(params: {
|
||||
backupVersion: null,
|
||||
};
|
||||
if (accountConfig.encryption === true) {
|
||||
const { maybeBootstrapNewEncryptedMatrixAccount } = await import("./setup-bootstrap.js");
|
||||
verificationBootstrap = await maybeBootstrapNewEncryptedMatrixAccount({
|
||||
previousCfg: cfg,
|
||||
cfg: updated,
|
||||
|
||||
92
extensions/matrix/src/matrix/client/env-auth.ts
Normal file
92
extensions/matrix/src/matrix/client/env-auth.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import { getMatrixScopedEnvVarNames } from "../../env-vars.js";
|
||||
|
||||
type MatrixEnvConfig = {
|
||||
homeserver: string;
|
||||
userId: string;
|
||||
accessToken?: string;
|
||||
password?: string;
|
||||
deviceId?: string;
|
||||
deviceName?: string;
|
||||
};
|
||||
|
||||
function clean(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function resolveGlobalMatrixEnvConfig(env: NodeJS.ProcessEnv): MatrixEnvConfig {
|
||||
return {
|
||||
homeserver: clean(env.MATRIX_HOMESERVER),
|
||||
userId: clean(env.MATRIX_USER_ID),
|
||||
accessToken: clean(env.MATRIX_ACCESS_TOKEN) || undefined,
|
||||
password: clean(env.MATRIX_PASSWORD) || undefined,
|
||||
deviceId: clean(env.MATRIX_DEVICE_ID) || undefined,
|
||||
deviceName: clean(env.MATRIX_DEVICE_NAME) || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveScopedMatrixEnvConfig(
|
||||
accountId: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): MatrixEnvConfig {
|
||||
const keys = getMatrixScopedEnvVarNames(accountId);
|
||||
return {
|
||||
homeserver: clean(env[keys.homeserver]),
|
||||
userId: clean(env[keys.userId]),
|
||||
accessToken: clean(env[keys.accessToken]) || undefined,
|
||||
password: clean(env[keys.password]) || undefined,
|
||||
deviceId: clean(env[keys.deviceId]) || undefined,
|
||||
deviceName: clean(env[keys.deviceName]) || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function hasReadyMatrixEnvAuth(config: {
|
||||
homeserver?: string;
|
||||
userId?: string;
|
||||
accessToken?: string;
|
||||
password?: string;
|
||||
}): boolean {
|
||||
const homeserver = clean(config.homeserver);
|
||||
const userId = clean(config.userId);
|
||||
const accessToken = clean(config.accessToken);
|
||||
const password = clean(config.password);
|
||||
return Boolean(homeserver && (accessToken || (userId && password)));
|
||||
}
|
||||
|
||||
export function resolveMatrixEnvAuthReadiness(
|
||||
accountId: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): {
|
||||
ready: boolean;
|
||||
homeserver?: string;
|
||||
userId?: string;
|
||||
sourceHint: string;
|
||||
missingMessage: string;
|
||||
} {
|
||||
const normalizedAccountId = normalizeAccountId(accountId);
|
||||
const scoped = resolveScopedMatrixEnvConfig(normalizedAccountId, env);
|
||||
if (normalizedAccountId !== DEFAULT_ACCOUNT_ID) {
|
||||
const keys = getMatrixScopedEnvVarNames(normalizedAccountId);
|
||||
return {
|
||||
ready: hasReadyMatrixEnvAuth(scoped),
|
||||
homeserver: scoped.homeserver || undefined,
|
||||
userId: scoped.userId || undefined,
|
||||
sourceHint: `${keys.homeserver} (+ auth vars)`,
|
||||
missingMessage: `Set per-account env vars for "${normalizedAccountId}" (for example ${keys.homeserver} + ${keys.accessToken} or ${keys.userId} + ${keys.password}).`,
|
||||
};
|
||||
}
|
||||
|
||||
const defaultScoped = resolveScopedMatrixEnvConfig(DEFAULT_ACCOUNT_ID, env);
|
||||
const global = resolveGlobalMatrixEnvConfig(env);
|
||||
const defaultKeys = getMatrixScopedEnvVarNames(DEFAULT_ACCOUNT_ID);
|
||||
return {
|
||||
ready: hasReadyMatrixEnvAuth(defaultScoped) || hasReadyMatrixEnvAuth(global),
|
||||
homeserver: defaultScoped.homeserver || global.homeserver || undefined,
|
||||
userId: defaultScoped.userId || global.userId || undefined,
|
||||
sourceHint: "MATRIX_* or MATRIX_DEFAULT_*",
|
||||
missingMessage:
|
||||
`Set Matrix env vars for the default account ` +
|
||||
`(for example MATRIX_HOMESERVER + MATRIX_ACCESS_TOKEN, MATRIX_USER_ID + MATRIX_PASSWORD, ` +
|
||||
`or ${defaultKeys.homeserver} + ${defaultKeys.accessToken}).`,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import { coerceSecretRef } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { normalizeAccountId, normalizeSecretInputString } from "../runtime-api.js";
|
||||
import { normalizeSecretInputString } from "openclaw/plugin-sdk/setup";
|
||||
import type { CoreConfig, MatrixConfig } from "../types.js";
|
||||
import { findMatrixAccountConfig } from "./account-config.js";
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
resolveMatrixAccountConfig,
|
||||
} from "./matrix/accounts.js";
|
||||
import {
|
||||
resolveMatrixEnvAuthReadiness,
|
||||
resolveValidatedMatrixHomeserverUrl,
|
||||
validateMatrixHomeserverUrl,
|
||||
} from "./matrix/client.js";
|
||||
import { resolveMatrixEnvAuthReadiness } from "./matrix/client/env-auth.js";
|
||||
import {
|
||||
resolveMatrixConfigFieldPath,
|
||||
resolveMatrixConfigPath,
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
type RuntimeEnv,
|
||||
type WizardPrompter,
|
||||
} from "./runtime-api.js";
|
||||
import { runMatrixSetupBootstrapAfterConfigWrite } from "./setup-bootstrap.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
const channel = "matrix" as const;
|
||||
@@ -644,6 +643,7 @@ export const matrixOnboardingAdapter: MatrixOnboardingAdapter = {
|
||||
});
|
||||
},
|
||||
afterConfigWritten: async ({ previousCfg, cfg, accountId, runtime }) => {
|
||||
const { runMatrixSetupBootstrapAfterConfigWrite } = await import("./setup-bootstrap.js");
|
||||
await runMatrixSetupBootstrapAfterConfigWrite({
|
||||
previousCfg: previousCfg as CoreConfig,
|
||||
cfg: cfg as CoreConfig,
|
||||
|
||||
@@ -1,16 +1,161 @@
|
||||
import { resolveMatrixEnvAuthReadiness } from "./matrix/client.js";
|
||||
import { updateMatrixAccountConfig } from "./matrix/config-update.js";
|
||||
import {
|
||||
applyAccountNameToChannelSection,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
moveSingleAccountChannelSectionToDefaultAccount,
|
||||
normalizeAccountId,
|
||||
normalizeSecretInputString,
|
||||
type ChannelSetupInput,
|
||||
} from "./runtime-api.js";
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { resolveMatrixEnvAuthReadiness } from "./matrix/client/env-auth.js";
|
||||
import { updateMatrixAccountConfig } from "./matrix/config-update.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
const channel = "matrix" as const;
|
||||
const COMMON_SINGLE_ACCOUNT_KEYS_TO_MOVE = new Set([
|
||||
"name",
|
||||
"enabled",
|
||||
"httpPort",
|
||||
"webhookPath",
|
||||
"webhookUrl",
|
||||
"webhookSecret",
|
||||
"service",
|
||||
"region",
|
||||
"homeserver",
|
||||
"userId",
|
||||
"accessToken",
|
||||
"password",
|
||||
"deviceName",
|
||||
"url",
|
||||
"code",
|
||||
"dmPolicy",
|
||||
"allowFrom",
|
||||
"groupPolicy",
|
||||
"groupAllowFrom",
|
||||
"defaultTo",
|
||||
]);
|
||||
const MATRIX_SINGLE_ACCOUNT_KEYS_TO_MOVE = new Set([
|
||||
"deviceId",
|
||||
"avatarUrl",
|
||||
"initialSyncLimit",
|
||||
"encryption",
|
||||
"allowlistOnly",
|
||||
"allowBots",
|
||||
"replyToMode",
|
||||
"threadReplies",
|
||||
"textChunkLimit",
|
||||
"chunkMode",
|
||||
"responsePrefix",
|
||||
"ackReaction",
|
||||
"ackReactionScope",
|
||||
"reactionNotifications",
|
||||
"threadBindings",
|
||||
"startupVerification",
|
||||
"startupVerificationCooldownHours",
|
||||
"mediaMaxMb",
|
||||
"autoJoin",
|
||||
"autoJoinAllowlist",
|
||||
"dm",
|
||||
"groups",
|
||||
"rooms",
|
||||
"actions",
|
||||
]);
|
||||
const MATRIX_NAMED_ACCOUNT_PROMOTION_KEYS = new Set([
|
||||
"name",
|
||||
"homeserver",
|
||||
"userId",
|
||||
"accessToken",
|
||||
"password",
|
||||
"deviceId",
|
||||
"deviceName",
|
||||
"avatarUrl",
|
||||
"initialSyncLimit",
|
||||
"encryption",
|
||||
]);
|
||||
|
||||
function cloneIfObject<T>(value: T): T {
|
||||
if (value && typeof value === "object") {
|
||||
return structuredClone(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function moveSingleMatrixAccountConfigToNamedAccount(cfg: CoreConfig): CoreConfig {
|
||||
const channels = cfg.channels as Record<string, unknown> | undefined;
|
||||
const baseConfig = channels?.[channel];
|
||||
const base =
|
||||
typeof baseConfig === "object" && baseConfig
|
||||
? (baseConfig as Record<string, unknown>)
|
||||
: undefined;
|
||||
if (!base) {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
const accounts =
|
||||
typeof base.accounts === "object" && base.accounts
|
||||
? (base.accounts as Record<string, Record<string, unknown>>)
|
||||
: {};
|
||||
const hasNamedAccounts = Object.keys(accounts).filter(Boolean).length > 0;
|
||||
const keysToMove = Object.entries(base)
|
||||
.filter(([key, value]) => {
|
||||
if (key === "accounts" || key === "enabled" || value === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!COMMON_SINGLE_ACCOUNT_KEYS_TO_MOVE.has(key) &&
|
||||
!MATRIX_SINGLE_ACCOUNT_KEYS_TO_MOVE.has(key)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (hasNamedAccounts && !MATRIX_NAMED_ACCOUNT_PROMOTION_KEYS.has(key)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(([key]) => key);
|
||||
if (keysToMove.length === 0) {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
const defaultAccount =
|
||||
typeof base.defaultAccount === "string" && base.defaultAccount.trim()
|
||||
? normalizeAccountId(base.defaultAccount)
|
||||
: undefined;
|
||||
const targetAccountId =
|
||||
defaultAccount && defaultAccount !== DEFAULT_ACCOUNT_ID
|
||||
? (Object.entries(accounts).find(
|
||||
([accountId, value]) =>
|
||||
accountId &&
|
||||
value &&
|
||||
typeof value === "object" &&
|
||||
normalizeAccountId(accountId) === defaultAccount,
|
||||
)?.[0] ?? DEFAULT_ACCOUNT_ID)
|
||||
: (defaultAccount ??
|
||||
(Object.keys(accounts).filter(Boolean).length === 1
|
||||
? Object.keys(accounts).filter(Boolean)[0]
|
||||
: DEFAULT_ACCOUNT_ID));
|
||||
|
||||
const nextAccount: Record<string, unknown> = { ...(accounts[targetAccountId] ?? {}) };
|
||||
for (const key of keysToMove) {
|
||||
nextAccount[key] = cloneIfObject(base[key]);
|
||||
}
|
||||
const nextChannel = { ...base };
|
||||
for (const key of keysToMove) {
|
||||
delete nextChannel[key];
|
||||
}
|
||||
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
[channel]: {
|
||||
...nextChannel,
|
||||
accounts: {
|
||||
...accounts,
|
||||
[targetAccountId]: nextAccount,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function validateMatrixSetupInput(params: {
|
||||
accountId: string;
|
||||
@@ -49,10 +194,7 @@ export function applyMatrixSetupAccountConfig(params: {
|
||||
const normalizedAccountId = normalizeAccountId(params.accountId);
|
||||
const migratedCfg =
|
||||
normalizedAccountId !== DEFAULT_ACCOUNT_ID
|
||||
? (moveSingleAccountChannelSectionToDefaultAccount({
|
||||
cfg: params.cfg,
|
||||
channelKey: channel,
|
||||
}) as CoreConfig)
|
||||
? moveSingleMatrixAccountConfigToNamedAccount(params.cfg)
|
||||
: params.cfg;
|
||||
const next = applyAccountNameToChannelSection({
|
||||
cfg: migratedCfg,
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
type ChannelSetupAdapter,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { updateMatrixAccountConfig } from "./matrix/config-update.js";
|
||||
import { runMatrixSetupBootstrapAfterConfigWrite } from "./setup-bootstrap.js";
|
||||
import { applyMatrixSetupAccountConfig, validateMatrixSetupInput } from "./setup-config.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
@@ -65,6 +64,7 @@ export const matrixSetupAdapter: ChannelSetupAdapter = {
|
||||
input,
|
||||
}),
|
||||
afterAccountConfigWritten: async ({ previousCfg, cfg, accountId, runtime }) => {
|
||||
const { runMatrixSetupBootstrapAfterConfigWrite } = await import("./setup-bootstrap.js");
|
||||
await runMatrixSetupBootstrapAfterConfigWrite({
|
||||
previousCfg: previousCfg as CoreConfig,
|
||||
cfg: cfg as CoreConfig,
|
||||
|
||||
Reference in New Issue
Block a user