@@ -273,4 +273,116 @@ describe("createNodesTool screen_record duration guardrails", () => {
273273 } ) ;
274274 expect ( JSON . stringify ( result ?. content ?? [ ] ) ) . not . toContain ( "MEDIA:" ) ;
275275 } ) ;
276+
277+ it ( "uses operator.admin to approve exec-capable node pair requests" , async ( ) => {
278+ gatewayMocks . callGatewayTool . mockImplementation ( async ( method , _opts , params , extra ) => {
279+ if ( method === "node.pair.list" ) {
280+ return {
281+ pending : [
282+ {
283+ requestId : "req-1" ,
284+ commands : [ "system.run" ] ,
285+ } ,
286+ ] ,
287+ } ;
288+ }
289+ if ( method === "node.pair.approve" ) {
290+ return { ok : true , method, params, extra } ;
291+ }
292+ throw new Error ( `unexpected method: ${ String ( method ) } ` ) ;
293+ } ) ;
294+ const tool = createNodesTool ( ) ;
295+
296+ await tool . execute ( "call-1" , {
297+ action : "approve" ,
298+ requestId : "req-1" ,
299+ } ) ;
300+
301+ expect ( gatewayMocks . callGatewayTool ) . toHaveBeenNthCalledWith (
302+ 1 ,
303+ "node.pair.list" ,
304+ { } ,
305+ { } ,
306+ { scopes : [ "operator.pairing" , "operator.write" ] } ,
307+ ) ;
308+ expect ( gatewayMocks . callGatewayTool ) . toHaveBeenNthCalledWith (
309+ 2 ,
310+ "node.pair.approve" ,
311+ { } ,
312+ { requestId : "req-1" } ,
313+ { scopes : [ "operator.admin" ] } ,
314+ ) ;
315+ } ) ;
316+
317+ it ( "uses operator.write to approve non-exec node pair requests" , async ( ) => {
318+ gatewayMocks . callGatewayTool . mockImplementation ( async ( method , _opts , params , extra ) => {
319+ if ( method === "node.pair.list" ) {
320+ return {
321+ pending : [
322+ {
323+ requestId : "req-1" ,
324+ commands : [ "canvas.snapshot" ] ,
325+ } ,
326+ ] ,
327+ } ;
328+ }
329+ if ( method === "node.pair.approve" ) {
330+ return { ok : true , method, params, extra } ;
331+ }
332+ throw new Error ( `unexpected method: ${ String ( method ) } ` ) ;
333+ } ) ;
334+ const tool = createNodesTool ( ) ;
335+
336+ await tool . execute ( "call-1" , {
337+ action : "approve" ,
338+ requestId : "req-1" ,
339+ } ) ;
340+
341+ expect ( gatewayMocks . callGatewayTool ) . toHaveBeenNthCalledWith (
342+ 1 ,
343+ "node.pair.list" ,
344+ { } ,
345+ { } ,
346+ { scopes : [ "operator.pairing" , "operator.write" ] } ,
347+ ) ;
348+ expect ( gatewayMocks . callGatewayTool ) . toHaveBeenNthCalledWith (
349+ 2 ,
350+ "node.pair.approve" ,
351+ { } ,
352+ { requestId : "req-1" } ,
353+ { scopes : [ "operator.write" ] } ,
354+ ) ;
355+ } ) ;
356+
357+ it ( "uses operator.write for commandless node pair requests" , async ( ) => {
358+ gatewayMocks . callGatewayTool . mockImplementation ( async ( method , _opts , params , extra ) => {
359+ if ( method === "node.pair.list" ) {
360+ return {
361+ pending : [
362+ {
363+ requestId : "req-1" ,
364+ } ,
365+ ] ,
366+ } ;
367+ }
368+ if ( method === "node.pair.approve" ) {
369+ return { ok : true , method, params, extra } ;
370+ }
371+ throw new Error ( `unexpected method: ${ String ( method ) } ` ) ;
372+ } ) ;
373+ const tool = createNodesTool ( ) ;
374+
375+ await tool . execute ( "call-1" , {
376+ action : "approve" ,
377+ requestId : "req-1" ,
378+ } ) ;
379+
380+ expect ( gatewayMocks . callGatewayTool ) . toHaveBeenNthCalledWith (
381+ 2 ,
382+ "node.pair.approve" ,
383+ { } ,
384+ { requestId : "req-1" } ,
385+ { scopes : [ "operator.write" ] } ,
386+ ) ;
387+ } ) ;
276388} ) ;
0 commit comments