import { feature } from 'bun:bundle' import { useEffect, useRef } from 'react' import { type AppState, useAppState, useAppStateStore, useSetAppState, } from 'src/state/AppState.js' import type { ToolPermissionContext } from 'src/Tool.js' import { getIsRemoteMode } from '../../bootstrap/state.js' import { createDisabledBypassPermissionsContext, shouldDisableBypassPermissions, verifyAutoModeGateAccess, } from './permissionSetup.js' let bypassPermissionsCheckRan = false export async function checkAndDisableBypassPermissionsIfNeeded( toolPermissionContext: ToolPermissionContext, setAppState: (f: (prev: AppState) => AppState) => void, ): Promise { // Check if bypassPermissions should be disabled based on Statsig gate // Do this only once, before the first query, to ensure we have the latest gate value if (bypassPermissionsCheckRan) { return } bypassPermissionsCheckRan = true if (!toolPermissionContext.isBypassPermissionsModeAvailable) { return } const shouldDisable = await shouldDisableBypassPermissions() if (!shouldDisable) { return } setAppState(prev => { return { ...prev, toolPermissionContext: createDisabledBypassPermissionsContext( prev.toolPermissionContext, ), } }) } /** * Reset the run-once flag for checkAndDisableBypassPermissionsIfNeeded. * Call this after /login so the gate check re-runs with the new org. */ export function resetBypassPermissionsCheck(): void { bypassPermissionsCheckRan = false } export function useKickOffCheckAndDisableBypassPermissionsIfNeeded(): void { const toolPermissionContext = useAppState(s => s.toolPermissionContext) const setAppState = useSetAppState() // Run once, when the component mounts useEffect(() => { if (getIsRemoteMode()) return void checkAndDisableBypassPermissionsIfNeeded( toolPermissionContext, setAppState, ) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) } let autoModeCheckRan = false export async function checkAndDisableAutoModeIfNeeded( toolPermissionContext: ToolPermissionContext, setAppState: (f: (prev: AppState) => AppState) => void, fastMode?: boolean, ): Promise { if (feature('TRANSCRIPT_CLASSIFIER')) { if (autoModeCheckRan) { return } autoModeCheckRan = true const { updateContext, notification } = await verifyAutoModeGateAccess( toolPermissionContext, fastMode, ) setAppState(prev => { // Apply the transform to CURRENT context, not the stale snapshot we // passed to verifyAutoModeGateAccess. The async GrowthBook await inside // can be outrun by a mid-turn shift-tab; spreading a stale context here // would revert the user's mode change. const nextCtx = updateContext(prev.toolPermissionContext) const newState = nextCtx === prev.toolPermissionContext ? prev : { ...prev, toolPermissionContext: nextCtx } if (!notification) return newState return { ...newState, notifications: { ...newState.notifications, queue: [ ...newState.notifications.queue, { key: 'auto-mode-gate-notification', text: notification, color: 'warning' as const, priority: 'high' as const, }, ], }, } }) } } /** * Reset the run-once flag for checkAndDisableAutoModeIfNeeded. * Call this after /login so the gate check re-runs with the new org. */ export function resetAutoModeGateCheck(): void { autoModeCheckRan = false } export function useKickOffCheckAndDisableAutoModeIfNeeded(): void { const mainLoopModel = useAppState(s => s.mainLoopModel) const mainLoopModelForSession = useAppState(s => s.mainLoopModelForSession) const fastMode = useAppState(s => s.fastMode) const setAppState = useSetAppState() const store = useAppStateStore() const isFirstRunRef = useRef(true) // Runs on mount (startup check) AND whenever the model or fast mode changes // (kick-out / carousel-restore). Watching both model fields covers /model, // Cmd+P picker, /config, and bridge onSetModel paths; fastMode covers // /fast on|off for the tengu_auto_mode_config.disableFastMode circuit // breaker. The print.ts headless paths are covered by the sync // isAutoModeGateEnabled() check. useEffect(() => { if (getIsRemoteMode()) return if (isFirstRunRef.current) { isFirstRunRef.current = false } else { resetAutoModeGateCheck() } void checkAndDisableAutoModeIfNeeded( store.getState().toolPermissionContext, setAppState, fastMode, ) // eslint-disable-next-line react-hooks/exhaustive-deps }, [mainLoopModel, mainLoopModelForSession, fastMode]) }