@@ -404,20 +404,16 @@ describe('Permission auto mode', () => {
404404 } ,
405405 ) ;
406406
407- it . each (
408- ( [ 'manual' , 'yolo' ] as const ) . flatMap ( ( mode ) =>
409- [
410- [ mode , 'Write' , { path : '/tmp/notes.md' , content : 'x' } , 'write' , 'write file' ] ,
411- [ mode , 'Edit' , { path : '/tmp/notes.md' , old_string : 'a' , new_string : 'b' } , 'edit' , 'edit file' ] ,
412- ] as const ,
413- ) ,
414- ) (
415- 'requests approval in %s mode for %s outside the cwd' ,
416- async ( mode , toolName , args , operation , action ) => {
417- const { manager, requestApproval } = makePermissionManager ( async ( ) => ( {
407+ it . each ( [
408+ [ 'Write' , { path : '/tmp/notes.md' , content : 'x' } , 'write' , 'write file' ] ,
409+ [ 'Edit' , { path : '/tmp/notes.md' , old_string : 'a' , new_string : 'b' } , 'edit' , 'edit file' ] ,
410+ ] as const ) (
411+ 'requests approval in manual mode for %s outside the cwd' ,
412+ async ( toolName , args , operation , action ) => {
413+ const { manager, requestApproval, telemetryTrack } = makePermissionManager ( async ( ) => ( {
418414 decision : 'approved' ,
419415 } ) ) ;
420- manager . setMode ( mode ) ;
416+ manager . setMode ( 'manual' ) ;
421417
422418 await expect (
423419 manager . beforeToolCall ( hookContext ( { id : `call_${ toolName } ` , toolName, args } ) ) ,
@@ -435,9 +431,43 @@ describe('Permission auto mode', () => {
435431 } ) ,
436432 expect . any ( Object ) ,
437433 ) ;
434+ expect ( telemetryTrack ) . toHaveBeenCalledWith (
435+ 'permission_policy_decision' ,
436+ expect . objectContaining ( {
437+ policy_name : 'fallback-ask' ,
438+ tool_name : toolName ,
439+ permission_mode : 'manual' ,
440+ decision : 'ask' ,
441+ } ) ,
442+ ) ;
438443 } ,
439444 ) ;
440445
446+ it . each ( [
447+ [ 'Write' , { path : '/tmp/notes.md' , content : 'x' } ] ,
448+ [ 'Edit' , { path : '/tmp/notes.md' , old_string : 'a' , new_string : 'b' } ] ,
449+ ] as const ) ( 'approves %s outside the cwd in yolo mode' , async ( toolName , args ) => {
450+ const { manager, requestApproval, telemetryTrack } = makePermissionManager ( async ( ) => ( {
451+ decision : 'approved' ,
452+ } ) ) ;
453+ manager . setMode ( 'yolo' ) ;
454+
455+ await expect (
456+ manager . beforeToolCall ( hookContext ( { id : `call_${ toolName } _yolo_outside` , toolName, args } ) ) ,
457+ ) . resolves . toBeUndefined ( ) ;
458+
459+ expect ( requestApproval ) . not . toHaveBeenCalled ( ) ;
460+ expect ( telemetryTrack ) . toHaveBeenCalledWith (
461+ 'permission_policy_decision' ,
462+ expect . objectContaining ( {
463+ policy_name : 'yolo-mode-approve' ,
464+ tool_name : toolName ,
465+ permission_mode : 'yolo' ,
466+ decision : 'approve' ,
467+ } ) ,
468+ ) ;
469+ } ) ;
470+
441471 it . each (
442472 ( [ 'manual' , 'yolo' ] as const ) . flatMap ( ( mode ) =>
443473 [
@@ -638,7 +668,7 @@ describe('Permission auto mode', () => {
638668 ) ;
639669 } ) ;
640670
641- it ( 'reuses approve-for-session for repeated outside-workspace writes in yolo mode' , async ( ) => {
671+ it ( 'approves repeated outside-workspace writes in yolo mode without session approval ' , async ( ) => {
642672 const { manager, requestApproval } = makePermissionManager ( async ( ) => ( {
643673 decision : 'approved' ,
644674 scope : 'session' ,
@@ -657,8 +687,8 @@ describe('Permission auto mode', () => {
657687 await expect ( call ( ) ) . resolves . toBeUndefined ( ) ;
658688 await expect ( call ( ) ) . resolves . toBeUndefined ( ) ;
659689
660- expect ( requestApproval ) . toHaveBeenCalledTimes ( 1 ) ;
661- expect ( manager . sessionApprovalRulePatterns ) . toEqual ( [ 'Write(/tmp/notes.md)' ] ) ;
690+ expect ( requestApproval ) . not . toHaveBeenCalled ( ) ;
691+ expect ( manager . sessionApprovalRulePatterns ) . toEqual ( [ ] ) ;
662692 expect ( manager . data ( ) . rules ) . toEqual ( [ ] ) ;
663693 } ) ;
664694} ) ;
@@ -678,7 +708,6 @@ describe('Permission policy chain', () => {
678708 'plan-mode-tool-approve' ,
679709 'sensitive-file-access-ask' ,
680710 'git-control-path-access-ask' ,
681- 'cwd-outside-file-write-ask' ,
682711 'yolo-mode-approve' ,
683712 'swarm-mode-agent-swarm-approve' ,
684713 'default-tool-approve' ,
@@ -3301,7 +3330,7 @@ describe('Default git CWD Write/Edit permission', () => {
33013330 expect ( requestApproval ) . toHaveBeenCalledTimes ( 1 ) ;
33023331 expect ( telemetryTrack ) . toHaveBeenCalledWith (
33033332 'permission_policy_decision' ,
3304- expect . objectContaining ( { policy_name : 'cwd-outside-file-write -ask' } ) ,
3333+ expect . objectContaining ( { policy_name : 'fallback -ask' } ) ,
33053334 ) ;
33063335 expect ( telemetryTrack ) . not . toHaveBeenCalledWith (
33073336 'permission_policy_decision' ,
@@ -3357,146 +3386,6 @@ describe('Default git CWD Write/Edit permission', () => {
33573386 } ) ;
33583387} ) ;
33593388
3360- describe ( 'CWD outside file write permission policy' , ( ) => {
3361- it ( 'falls through when cwd is empty' , async ( ) => {
3362- const { manager, requestApproval, telemetryTrack } = makePermissionManager (
3363- async ( ) => ( { decision : 'approved' } ) ,
3364- { cwd : '' } ,
3365- ) ;
3366-
3367- await expect (
3368- manager . beforeToolCall (
3369- hookContext ( {
3370- id : 'call_empty_cwd_write' ,
3371- toolName : 'Write' ,
3372- args : { path : '/tmp/outside.ts' , content : 'x' } ,
3373- } ) ,
3374- ) ,
3375- ) . resolves . toBeUndefined ( ) ;
3376-
3377- expect ( requestApproval ) . toHaveBeenCalledTimes ( 1 ) ;
3378- expect ( telemetryTrack ) . toHaveBeenCalledWith (
3379- 'permission_policy_decision' ,
3380- expect . objectContaining ( { policy_name : 'fallback-ask' } ) ,
3381- ) ;
3382- } ) ;
3383-
3384- it ( 'falls through when there are no file write accesses' , async ( ) => {
3385- const args = { path : '/tmp/outside.ts' , content : 'x' } ;
3386- const { manager, requestApproval, telemetryTrack } = makePermissionManager ( async ( ) => ( {
3387- decision : 'approved' ,
3388- } ) ) ;
3389-
3390- await expect (
3391- manager . beforeToolCall (
3392- hookContext ( {
3393- id : 'call_no_write_accesses' ,
3394- toolName : 'Write' ,
3395- args,
3396- execution : {
3397- ...testExecution ( 'Write' , args ) ,
3398- accesses : ToolAccesses . none ( ) ,
3399- } ,
3400- } ) ,
3401- ) ,
3402- ) . resolves . toBeUndefined ( ) ;
3403-
3404- expect ( requestApproval ) . toHaveBeenCalledTimes ( 1 ) ;
3405- expect ( telemetryTrack ) . toHaveBeenCalledWith (
3406- 'permission_policy_decision' ,
3407- expect . objectContaining ( { policy_name : 'fallback-ask' } ) ,
3408- ) ;
3409- } ) ;
3410-
3411- it ( 'asks when any write access is outside the cwd' , async ( ) => {
3412- const args = { path : '/workspace/src/a.ts' , content : 'x' } ;
3413- const { manager, requestApproval, telemetryTrack } = makePermissionManager ( async ( ) => ( {
3414- decision : 'approved' ,
3415- } ) ) ;
3416-
3417- await expect (
3418- manager . beforeToolCall (
3419- hookContext ( {
3420- id : 'call_mixed_write_accesses' ,
3421- toolName : 'Write' ,
3422- args,
3423- execution : {
3424- ...testExecution ( 'Write' , args ) ,
3425- accesses : [
3426- { kind : 'file' , operation : 'write' , path : '/workspace/src/a.ts' } ,
3427- { kind : 'file' , operation : 'readwrite' , path : '/tmp/outside.ts' } ,
3428- ] ,
3429- } ,
3430- } ) ,
3431- ) ,
3432- ) . resolves . toBeUndefined ( ) ;
3433-
3434- expect ( requestApproval ) . toHaveBeenCalledTimes ( 1 ) ;
3435- expect ( telemetryTrack ) . toHaveBeenCalledWith (
3436- 'permission_policy_decision' ,
3437- expect . objectContaining ( {
3438- policy_name : 'cwd-outside-file-write-ask' ,
3439- decision : 'ask' ,
3440- cwd_outside : true ,
3441- file_access_operation : 'readwrite' ,
3442- } ) ,
3443- ) ;
3444- } ) ;
3445-
3446- it . each ( [
3447- [ 'Read' , { path : '/tmp/outside.ts' } ] ,
3448- [ 'Grep' , { pattern : 'TODO' , path : '/tmp' } ] ,
3449- ] as const ) ( 'does not ask for %s access outside cwd' , async ( toolName , args ) => {
3450- const { manager, requestApproval, telemetryTrack } = makePermissionManager ( async ( ) => ( {
3451- decision : 'approved' ,
3452- } ) ) ;
3453-
3454- await expect (
3455- manager . beforeToolCall (
3456- hookContext ( {
3457- id : `call_${ toolName } _outside_cwd` ,
3458- toolName,
3459- args,
3460- } ) ,
3461- ) ,
3462- ) . resolves . toBeUndefined ( ) ;
3463-
3464- expect ( requestApproval ) . not . toHaveBeenCalled ( ) ;
3465- expect ( telemetryTrack ) . toHaveBeenCalledWith (
3466- 'permission_policy_decision' ,
3467- expect . objectContaining ( { policy_name : 'default-tool-approve' } ) ,
3468- ) ;
3469- } ) ;
3470-
3471- it ( 'uses Win32 path semantics for cwd containment' , async ( ) => {
3472- const kaos = createFakeKaos ( { pathClass : ( ) => 'win32' } ) ;
3473- const args = { path : 'c:\\repo\\src\\a.ts' , content : 'x' } ;
3474- const { manager, telemetryTrack } = makePermissionManager (
3475- async ( ) => ( { decision : 'approved' } ) ,
3476- { cwd : 'C:\\Repo' , kaos } ,
3477- ) ;
3478-
3479- await expect (
3480- manager . beforeToolCall (
3481- hookContext ( {
3482- id : 'call_win_inside_cwd' ,
3483- toolName : 'Write' ,
3484- args,
3485- execution : {
3486- ...testExecution ( 'Write' , args ) ,
3487- accesses : ToolAccesses . writeFile ( 'c:\\repo\\src\\a.ts' ) ,
3488- } ,
3489- } ) ,
3490- ) ,
3491- ) . resolves . toBeUndefined ( ) ;
3492-
3493- expect ( telemetryTrack ) . not . toHaveBeenCalledWith (
3494- 'permission_policy_decision' ,
3495- expect . objectContaining ( { policy_name : 'cwd-outside-file-write-ask' } ) ,
3496- ) ;
3497- } ) ;
3498- } ) ;
3499-
35003389describe ( 'Permission rule helpers' , ( ) => {
35013390 it ( 'parses permission patterns used by rule matching' , ( ) => {
35023391 expect ( parsePattern ( 'Write' ) ) . toEqual ( { toolName : 'Write' } ) ;
0 commit comments