@@ -29,6 +29,7 @@ const callGatewayFromCliMock = vi.fn();
2929
3030type Registered = {
3131 methods : Map < string , unknown > ;
32+ methodScopes : Map < string , string | undefined > ;
3233 tools : unknown [ ] ;
3334 service ?: Parameters < OpenClawPluginApi [ "registerService" ] > [ 0 ] ;
3435} ;
@@ -108,6 +109,7 @@ function createServiceContext(): Parameters<NonNullable<Registered["service"]>["
108109
109110function setup ( config : Record < string , unknown > ) : Registered {
110111 const methods = new Map < string , unknown > ( ) ;
112+ const methodScopes = new Map < string , string | undefined > ( ) ;
111113 const tools : unknown [ ] = [ ] ;
112114 let service : Registered [ "service" ] ;
113115 const api = createTestPluginApi ( {
@@ -120,7 +122,10 @@ function setup(config: Record<string, unknown>): Registered {
120122 pluginConfig : config ,
121123 runtime : { tts : { textToSpeechTelephony : vi . fn ( ) } } as unknown as OpenClawPluginApi [ "runtime" ] ,
122124 logger : noopLogger ,
123- registerGatewayMethod : ( method : string , handler : unknown ) => methods . set ( method , handler ) ,
125+ registerGatewayMethod : ( method : string , handler : unknown , opts ?: { scope ?: string } ) => {
126+ methods . set ( method , handler ) ;
127+ methodScopes . set ( method , opts ?. scope ) ;
128+ } ,
124129 registerTool : ( tool : unknown ) => tools . push ( tool ) ,
125130 registerCli : ( ) => { } ,
126131 registerService : ( registeredService ) => {
@@ -129,7 +134,7 @@ function setup(config: Record<string, unknown>): Registered {
129134 resolvePath : ( p : string ) => p ,
130135 } ) ;
131136 plugin . register ( api ) ;
132- return { methods, tools, service } ;
137+ return { methods, methodScopes , tools, service } ;
133138}
134139
135140function envRef ( id : string ) {
@@ -363,6 +368,24 @@ describe("voice-call plugin", () => {
363368 expect ( payload . callId ) . toBe ( "call-1" ) ;
364369 } ) ;
365370
371+ it ( "registers voice call gateway methods with least-privilege scopes" , ( ) => {
372+ const { methodScopes } = setup ( { provider : "mock" } ) ;
373+
374+ for ( const method of [
375+ "voicecall.initiate" ,
376+ "voicecall.start" ,
377+ "voicecall.continue" ,
378+ "voicecall.continue.start" ,
379+ "voicecall.speak" ,
380+ "voicecall.dtmf" ,
381+ "voicecall.end" ,
382+ ] ) {
383+ expect ( methodScopes . get ( method ) ) . toBe ( "operator.write" ) ;
384+ }
385+ expect ( methodScopes . get ( "voicecall.continue.result" ) ) . toBe ( "operator.read" ) ;
386+ expect ( methodScopes . get ( "voicecall.status" ) ) . toBe ( "operator.read" ) ;
387+ } ) ;
388+
366389 it ( "preserves mode on legacy voicecall.start" , async ( ) => {
367390 const { methods } = setup ( { provider : "mock" } ) ;
368391 const handler = methods . get ( "voicecall.start" ) as
0 commit comments