getNextPermissionMode.ts
1 import { feature } from 'bun:bundle' 2 import type { ToolPermissionContext } from '../../Tool.js' 3 import { logForDebugging } from '../debug.js' 4 import type { PermissionMode } from './PermissionMode.js' 5 import { 6 getAutoModeUnavailableReason, 7 isAutoModeGateEnabled, 8 transitionPermissionMode, 9 } from './permissionSetup.js' 10 11 // Checks both the cached isAutoModeAvailable (set at startup by 12 // verifyAutoModeGateAccess) and the live isAutoModeGateEnabled() — these can 13 // diverge if the circuit breaker or settings change mid-session. The 14 // live check prevents transitionPermissionMode from throwing 15 // (permissionSetup.ts:~559), which would silently crash the shift+tab handler 16 // and leave the user stuck at the current mode. 17 function canCycleToAuto(ctx: ToolPermissionContext): boolean { 18 if (feature('TRANSCRIPT_CLASSIFIER')) { 19 const gateEnabled = isAutoModeGateEnabled() 20 const can = !!ctx.isAutoModeAvailable && gateEnabled 21 if (!can) { 22 logForDebugging( 23 `[auto-mode] canCycleToAuto=false: ctx.isAutoModeAvailable=${ctx.isAutoModeAvailable} isAutoModeGateEnabled=${gateEnabled} reason=${getAutoModeUnavailableReason()}`, 24 ) 25 } 26 return can 27 } 28 return false 29 } 30 31 /** 32 * Determines the next permission mode when cycling through modes with Shift+Tab. 33 */ 34 export function getNextPermissionMode( 35 toolPermissionContext: ToolPermissionContext, 36 _teamContext?: { leadAgentId: string }, 37 ): PermissionMode { 38 switch (toolPermissionContext.mode) { 39 case 'default': 40 // Ants skip acceptEdits and plan — auto mode replaces them 41 if (process.env.USER_TYPE === 'ant') { 42 if (toolPermissionContext.isBypassPermissionsModeAvailable) { 43 return 'bypassPermissions' 44 } 45 if (canCycleToAuto(toolPermissionContext)) { 46 return 'auto' 47 } 48 return 'default' 49 } 50 return 'acceptEdits' 51 52 case 'acceptEdits': 53 return 'plan' 54 55 case 'plan': 56 if (toolPermissionContext.isBypassPermissionsModeAvailable) { 57 return 'bypassPermissions' 58 } 59 if (canCycleToAuto(toolPermissionContext)) { 60 return 'auto' 61 } 62 return 'default' 63 64 case 'bypassPermissions': 65 if (canCycleToAuto(toolPermissionContext)) { 66 return 'auto' 67 } 68 return 'default' 69 70 case 'dontAsk': 71 // Not exposed in UI cycle yet, but return default if somehow reached 72 return 'default' 73 74 75 default: 76 // Covers auto (when TRANSCRIPT_CLASSIFIER is enabled) and any future modes — always fall back to default 77 return 'default' 78 } 79 } 80 81 /** 82 * Computes the next permission mode and prepares the context for it. 83 * Handles any context cleanup needed for the target mode (e.g., stripping 84 * dangerous permissions when entering auto mode). 85 * 86 * @returns The next mode and the context to use (with dangerous permissions stripped if needed) 87 */ 88 export function cyclePermissionMode( 89 toolPermissionContext: ToolPermissionContext, 90 teamContext?: { leadAgentId: string }, 91 ): { nextMode: PermissionMode; context: ToolPermissionContext } { 92 const nextMode = getNextPermissionMode(toolPermissionContext, teamContext) 93 return { 94 nextMode, 95 context: transitionPermissionMode( 96 toolPermissionContext.mode, 97 nextMode, 98 toolPermissionContext, 99 ), 100 } 101 }