fix: deep-merge nested config, prefer default account in send fallback, simplify credential filenames

This commit is contained in:
Monty Taylor
2026-02-09 08:33:58 -07:00
committed by Peter Steinberger
parent 1a72902991
commit da00f6cf8e
3 changed files with 36 additions and 8 deletions

View File

@@ -9,6 +9,29 @@ function clean(value?: string): string {
return value?.trim() ?? "";
}
/** Shallow-merge known nested config sub-objects so partial overrides inherit base values. */
function deepMergeConfig(
base: Record<string, unknown>,
override: Record<string, unknown>,
): Record<string, unknown> {
const merged = { ...base, ...override };
// Merge known nested objects (dm, actions) so partial overrides keep base fields
for (const key of ["dm", "actions"] as const) {
if (
typeof base[key] === "object" &&
base[key] !== null &&
typeof override[key] === "object" &&
override[key] !== null
) {
merged[key] = {
...(base[key] as Record<string, unknown>),
...(override[key] as Record<string, unknown>),
};
}
}
return merged;
}
/**
* Resolve Matrix config for a specific account, with fallback to top-level config.
* This supports both multi-account (channels.matrix.accounts.*) and
@@ -34,10 +57,10 @@ export function resolveMatrixConfigForAccount(
}
}
// Merge: account-specific values override top-level values
// For DEFAULT_ACCOUNT_ID with no accounts, use top-level directly
// Deep merge: account-specific values override top-level values, preserving
// nested object inheritance (dm, actions, groups) so partial overrides work.
const useAccountConfig = accountConfig !== undefined;
const matrix = useAccountConfig ? { ...matrixBase, ...accountConfig } : matrixBase;
const matrix = useAccountConfig ? deepMergeConfig(matrixBase, accountConfig) : matrixBase;
const homeserver = clean(matrix.homeserver) || clean(env.MATRIX_HOMESERVER);
const userId = clean(matrix.userId) || clean(env.MATRIX_USER_ID);

View File

@@ -18,9 +18,9 @@ function credentialsFilename(accountId?: string | null): string {
if (normalized === DEFAULT_ACCOUNT_ID) {
return "credentials.json";
}
// Sanitize accountId for use in filename
const safe = normalized.replace(/[^a-zA-Z0-9_-]/g, "_");
return `credentials-${safe}.json`;
// normalizeAccountId produces lowercase [a-z0-9-] strings, already filesystem-safe.
// Different raw IDs that normalize to the same value are the same logical account.
return `credentials-${normalized}.json`;
}
export function resolveMatrixCredentialsDir(

View File

@@ -1,5 +1,5 @@
import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
import { normalizeAccountId } from "openclaw/plugin-sdk";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
import type { CoreConfig } from "../../types.js";
import { getMatrixRuntime } from "../../runtime.js";
import { getActiveMatrixClient, getAnyActiveMatrixClient } from "../active-client.js";
@@ -67,8 +67,13 @@ export async function resolveMatrixClient(opts: {
if (active) {
return { client: active, stopOnDone: false };
}
// Only fall back to any active client when no specific account is requested
// When no account is specified, try the default account first; only fall back to
// any active client as a last resort (prevents sending from an arbitrary account).
if (!opts.accountId) {
const defaultClient = getActiveMatrixClient(DEFAULT_ACCOUNT_ID);
if (defaultClient) {
return { client: defaultClient, stopOnDone: false };
}
const anyActive = getAnyActiveMatrixClient();
if (anyActive) {
return { client: anyActive, stopOnDone: false };