@@ -194,6 +194,48 @@ describe("message action capability checks", () => {
194194 ) . toHaveProperty ( "components" ) ;
195195 } ) ;
196196
197+ it ( "keeps contributed schema properties optional so only action stays required" , ( ) => {
198+ const contributingPlugin : ChannelPlugin = {
199+ ...createChannelTestPluginBase ( {
200+ id : "demo-contrib" ,
201+ label : "Demo Contrib" ,
202+ capabilities : { chatTypes : [ "direct" , "group" ] } ,
203+ config : {
204+ listAccountIds : ( ) => [ "default" ] ,
205+ } ,
206+ } ) ,
207+ actions : {
208+ describeMessageTool : ( ) => ( {
209+ actions : [ "send" ] ,
210+ schema : {
211+ properties : {
212+ // Non-optional TypeBox schema: plugin forgot Type.Optional.
213+ components : Type . Array ( Type . String ( ) ) ,
214+ // Cloning strips typebox's non-enumerable `~optional` marker;
215+ // mirrors serialized/external plugin contributions.
216+ chatRef : structuredClone ( Type . Optional ( Type . String ( ) ) ) ,
217+ media : Type . Optional ( Type . String ( ) ) ,
218+ } ,
219+ } ,
220+ } ) ,
221+ } ,
222+ } ;
223+ setActivePluginRegistry (
224+ createTestRegistry ( [
225+ { pluginId : "demo-contrib" , source : "test" , plugin : contributingPlugin } ,
226+ ] ) ,
227+ ) ;
228+
229+ const properties = resolveChannelMessageToolSchemaProperties ( {
230+ cfg : { } as OpenClawConfig ,
231+ channel : "demo-contrib" ,
232+ } ) ;
233+ // Regression: required leakage made every message tool call fail validation
234+ // with "must have required properties chatRef, media, ...".
235+ const toolSchema = Type . Object ( { action : Type . String ( ) , ...properties } ) ;
236+ expect ( toolSchema . required ) . toEqual ( [ "action" ] ) ;
237+ } ) ;
238+
197239 it ( "filters only actions that depend on current-channel-only schema" , ( ) => {
198240 const scopedSchemaPlugin : ChannelPlugin = {
199241 ...createChannelTestPluginBase ( {
0 commit comments