@@ -7,6 +7,12 @@ import {
77} from "openclaw/plugin-sdk/setup" ;
88import { resolveMatrixEnvAuthReadiness } from "./matrix/client/env-auth.js" ;
99import { updateMatrixAccountConfig } from "./matrix/config-update.js" ;
10+ import { isSupportedMatrixAvatarSource } from "./matrix/profile.js" ;
11+ import {
12+ matrixNamedAccountPromotionKeys ,
13+ resolveSingleAccountPromotionTarget ,
14+ matrixSingleAccountKeysToMove ,
15+ } from "./setup-contract.js" ;
1016import type { CoreConfig } from "./types.js" ;
1117
1218const channel = "matrix" as const ;
@@ -32,48 +38,8 @@ const COMMON_SINGLE_ACCOUNT_KEYS_TO_MOVE = new Set([
3238 "groupAllowFrom" ,
3339 "defaultTo" ,
3440] ) ;
35- const MATRIX_SINGLE_ACCOUNT_KEYS_TO_MOVE = new Set ( [
36- "deviceId" ,
37- "avatarUrl" ,
38- "initialSyncLimit" ,
39- "encryption" ,
40- "allowlistOnly" ,
41- "allowBots" ,
42- "blockStreaming" ,
43- "replyToMode" ,
44- "threadReplies" ,
45- "textChunkLimit" ,
46- "chunkMode" ,
47- "responsePrefix" ,
48- "ackReaction" ,
49- "ackReactionScope" ,
50- "reactionNotifications" ,
51- "threadBindings" ,
52- "startupVerification" ,
53- "startupVerificationCooldownHours" ,
54- "mediaMaxMb" ,
55- "autoJoin" ,
56- "autoJoinAllowlist" ,
57- "dm" ,
58- "groups" ,
59- "rooms" ,
60- "actions" ,
61- ] ) ;
62- const MATRIX_NAMED_ACCOUNT_PROMOTION_KEYS = new Set ( [
63- // When named accounts already exist, only move auth/bootstrap fields into the
64- // promoted account. Delivery-policy fields stay at the top level so they
65- // remain shared inherited defaults for every account.
66- "name" ,
67- "homeserver" ,
68- "userId" ,
69- "accessToken" ,
70- "password" ,
71- "deviceId" ,
72- "deviceName" ,
73- "avatarUrl" ,
74- "initialSyncLimit" ,
75- "encryption" ,
76- ] ) ;
41+ const MATRIX_SINGLE_ACCOUNT_KEYS_TO_MOVE = new Set < string > ( matrixSingleAccountKeysToMove ) ;
42+ const MATRIX_NAMED_ACCOUNT_PROMOTION_KEYS = new Set < string > ( matrixNamedAccountPromotionKeys ) ;
7743
7844function cloneIfObject < T > ( value : T ) : T {
7945 if ( value && typeof value === "object" ) {
@@ -82,7 +48,16 @@ function cloneIfObject<T>(value: T): T {
8248 return value ;
8349}
8450
85- function moveSingleMatrixAccountConfigToNamedAccount ( cfg : CoreConfig ) : CoreConfig {
51+ function resolveSetupAvatarUrl ( input : ChannelSetupInput ) : string | undefined {
52+ const avatarUrl = input . avatarUrl ;
53+ if ( typeof avatarUrl !== "string" ) {
54+ return undefined ;
55+ }
56+ const trimmed = avatarUrl . trim ( ) ;
57+ return trimmed || undefined ;
58+ }
59+
60+ export function moveSingleMatrixAccountConfigToNamedAccount ( cfg : CoreConfig ) : CoreConfig {
8661 const channels = cfg . channels as Record < string , unknown > | undefined ;
8762 const baseConfig = channels ?. [ channel ] ;
8863 const base =
@@ -119,23 +94,7 @@ function moveSingleMatrixAccountConfigToNamedAccount(cfg: CoreConfig): CoreConfi
11994 return cfg ;
12095 }
12196
122- const defaultAccount =
123- typeof base . defaultAccount === "string" && base . defaultAccount . trim ( )
124- ? normalizeAccountId ( base . defaultAccount )
125- : undefined ;
126- const targetAccountId =
127- defaultAccount && defaultAccount !== DEFAULT_ACCOUNT_ID
128- ? ( Object . entries ( accounts ) . find (
129- ( [ accountId , value ] ) =>
130- accountId &&
131- value &&
132- typeof value === "object" &&
133- normalizeAccountId ( accountId ) === defaultAccount ,
134- ) ?. [ 0 ] ?? DEFAULT_ACCOUNT_ID )
135- : ( defaultAccount ??
136- ( Object . keys ( accounts ) . filter ( Boolean ) . length === 1
137- ? Object . keys ( accounts ) . filter ( Boolean ) [ 0 ]
138- : DEFAULT_ACCOUNT_ID ) ) ;
97+ const targetAccountId = resolveSingleAccountPromotionTarget ( { channel : base } ) ;
13998
14099 const nextAccount : Record < string , unknown > = { ...( accounts [ targetAccountId ] ?? { } ) } ;
141100 for ( const key of keysToMove ) {
@@ -165,6 +124,10 @@ export function validateMatrixSetupInput(params: {
165124 accountId : string ;
166125 input : ChannelSetupInput ;
167126} ) : string | null {
127+ const avatarUrl = resolveSetupAvatarUrl ( params . input ) ;
128+ if ( avatarUrl && ! isSupportedMatrixAvatarSource ( avatarUrl ) ) {
129+ return "Matrix avatar URL must be an mxc:// URI or an http(s) URL." ;
130+ }
168131 if ( params . input . useEnv ) {
169132 const envReadiness = resolveMatrixEnvAuthReadiness ( params . accountId , process . env ) ;
170133 return envReadiness . ready ? null : envReadiness . missingMessage ;
@@ -193,7 +156,6 @@ export function applyMatrixSetupAccountConfig(params: {
193156 cfg : CoreConfig ;
194157 accountId : string ;
195158 input : ChannelSetupInput ;
196- avatarUrl ?: string ;
197159} ) : CoreConfig {
198160 const normalizedAccountId = normalizeAccountId ( params . accountId ) ;
199161 const migratedCfg =
@@ -206,6 +168,7 @@ export function applyMatrixSetupAccountConfig(params: {
206168 accountId : normalizedAccountId ,
207169 name : params . input . name ,
208170 } ) as CoreConfig ;
171+ const avatarUrl = resolveSetupAvatarUrl ( params . input ) ;
209172
210173 if ( params . input . useEnv ) {
211174 return updateMatrixAccountConfig ( next , normalizedAccountId , {
@@ -218,6 +181,7 @@ export function applyMatrixSetupAccountConfig(params: {
218181 password : null ,
219182 deviceId : null ,
220183 deviceName : null ,
184+ avatarUrl,
221185 } ) ;
222186 }
223187
@@ -238,7 +202,7 @@ export function applyMatrixSetupAccountConfig(params: {
238202 accessToken : accessToken || ( password ? null : undefined ) ,
239203 password : password || ( accessToken ? null : undefined ) ,
240204 deviceName : params . input . deviceName ?. trim ( ) ,
241- avatarUrl : params . avatarUrl ,
205+ avatarUrl,
242206 initialSyncLimit : params . input . initialSyncLimit ,
243207 } ) ;
244208}
0 commit comments