102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
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,
|
|
),
|
|
}
|
|
}
|