import { feature } from 'bun:bundle' import type { ToolPermissionContext } from '../../Tool.js' import { logForDebugging } from '../debug.js' import type { PermissionMode } from './PermissionMode.js' import { getAutoModeUnavailableReason, isAutoModeGateEnabled, transitionPermissionMode, } from './permissionSetup.js' // Checks both the cached isAutoModeAvailable (set at startup by // verifyAutoModeGateAccess) and the live isAutoModeGateEnabled() — these can // diverge if the circuit breaker or settings change mid-session. The // live check prevents transitionPermissionMode from throwing // (permissionSetup.ts:~559), which would silently crash the shift+tab handler // and leave the user stuck at the current mode. function canCycleToAuto(ctx: ToolPermissionContext): boolean { if (feature('TRANSCRIPT_CLASSIFIER')) { const gateEnabled = isAutoModeGateEnabled() const can = !!ctx.isAutoModeAvailable && gateEnabled if (!can) { logForDebugging( `[auto-mode] canCycleToAuto=false: ctx.isAutoModeAvailable=${ctx.isAutoModeAvailable} isAutoModeGateEnabled=${gateEnabled} reason=${getAutoModeUnavailableReason()}`, ) } return can } return false } /** * Determines the next permission mode when cycling through modes with Shift+Tab. */ export function getNextPermissionMode( toolPermissionContext: ToolPermissionContext, _teamContext?: { leadAgentId: string }, ): PermissionMode { switch (toolPermissionContext.mode) { case 'default': // Ants skip acceptEdits and plan — auto mode replaces them if (process.env.USER_TYPE === 'ant') { if (toolPermissionContext.isBypassPermissionsModeAvailable) { return 'bypassPermissions' } if (canCycleToAuto(toolPermissionContext)) { return 'auto' } return 'default' } return 'acceptEdits' case 'acceptEdits': return 'plan' case 'plan': if (toolPermissionContext.isBypassPermissionsModeAvailable) { return 'bypassPermissions' } if (canCycleToAuto(toolPermissionContext)) { return 'auto' } return 'default' case 'bypassPermissions': if (canCycleToAuto(toolPermissionContext)) { return 'auto' } return 'default' case 'dontAsk': // Not exposed in UI cycle yet, but return default if somehow reached return 'default' default: // Covers auto (when TRANSCRIPT_CLASSIFIER is enabled) and any future modes — always fall back to default return 'default' } } /** * Computes the next permission mode and prepares the context for it. * Handles any context cleanup needed for the target mode (e.g., stripping * dangerous permissions when entering auto mode). * * @returns The next mode and the context to use (with dangerous permissions stripped if needed) */ export function cyclePermissionMode( toolPermissionContext: ToolPermissionContext, teamContext?: { leadAgentId: string }, ): { nextMode: PermissionMode; context: ToolPermissionContext } { const nextMode = getNextPermissionMode(toolPermissionContext, teamContext) return { nextMode, context: transitionPermissionMode( toolPermissionContext.mode, nextMode, toolPermissionContext, ), } }