source dump of claude code
at main 155 lines 4.8 kB view raw
1import { feature } from 'bun:bundle' 2import { useEffect, useRef } from 'react' 3import { 4 type AppState, 5 useAppState, 6 useAppStateStore, 7 useSetAppState, 8} from 'src/state/AppState.js' 9import type { ToolPermissionContext } from 'src/Tool.js' 10import { getIsRemoteMode } from '../../bootstrap/state.js' 11import { 12 createDisabledBypassPermissionsContext, 13 shouldDisableBypassPermissions, 14 verifyAutoModeGateAccess, 15} from './permissionSetup.js' 16 17let bypassPermissionsCheckRan = false 18 19export async function checkAndDisableBypassPermissionsIfNeeded( 20 toolPermissionContext: ToolPermissionContext, 21 setAppState: (f: (prev: AppState) => AppState) => void, 22): Promise<void> { 23 // Check if bypassPermissions should be disabled based on Statsig gate 24 // Do this only once, before the first query, to ensure we have the latest gate value 25 if (bypassPermissionsCheckRan) { 26 return 27 } 28 bypassPermissionsCheckRan = true 29 30 if (!toolPermissionContext.isBypassPermissionsModeAvailable) { 31 return 32 } 33 34 const shouldDisable = await shouldDisableBypassPermissions() 35 if (!shouldDisable) { 36 return 37 } 38 39 setAppState(prev => { 40 return { 41 ...prev, 42 toolPermissionContext: createDisabledBypassPermissionsContext( 43 prev.toolPermissionContext, 44 ), 45 } 46 }) 47} 48 49/** 50 * Reset the run-once flag for checkAndDisableBypassPermissionsIfNeeded. 51 * Call this after /login so the gate check re-runs with the new org. 52 */ 53export function resetBypassPermissionsCheck(): void { 54 bypassPermissionsCheckRan = false 55} 56 57export function useKickOffCheckAndDisableBypassPermissionsIfNeeded(): void { 58 const toolPermissionContext = useAppState(s => s.toolPermissionContext) 59 const setAppState = useSetAppState() 60 61 // Run once, when the component mounts 62 useEffect(() => { 63 if (getIsRemoteMode()) return 64 void checkAndDisableBypassPermissionsIfNeeded( 65 toolPermissionContext, 66 setAppState, 67 ) 68 // eslint-disable-next-line react-hooks/exhaustive-deps 69 }, []) 70} 71 72let autoModeCheckRan = false 73 74export async function checkAndDisableAutoModeIfNeeded( 75 toolPermissionContext: ToolPermissionContext, 76 setAppState: (f: (prev: AppState) => AppState) => void, 77 fastMode?: boolean, 78): Promise<void> { 79 if (feature('TRANSCRIPT_CLASSIFIER')) { 80 if (autoModeCheckRan) { 81 return 82 } 83 autoModeCheckRan = true 84 85 const { updateContext, notification } = await verifyAutoModeGateAccess( 86 toolPermissionContext, 87 fastMode, 88 ) 89 setAppState(prev => { 90 // Apply the transform to CURRENT context, not the stale snapshot we 91 // passed to verifyAutoModeGateAccess. The async GrowthBook await inside 92 // can be outrun by a mid-turn shift-tab; spreading a stale context here 93 // would revert the user's mode change. 94 const nextCtx = updateContext(prev.toolPermissionContext) 95 const newState = 96 nextCtx === prev.toolPermissionContext 97 ? prev 98 : { ...prev, toolPermissionContext: nextCtx } 99 if (!notification) return newState 100 return { 101 ...newState, 102 notifications: { 103 ...newState.notifications, 104 queue: [ 105 ...newState.notifications.queue, 106 { 107 key: 'auto-mode-gate-notification', 108 text: notification, 109 color: 'warning' as const, 110 priority: 'high' as const, 111 }, 112 ], 113 }, 114 } 115 }) 116 } 117} 118 119/** 120 * Reset the run-once flag for checkAndDisableAutoModeIfNeeded. 121 * Call this after /login so the gate check re-runs with the new org. 122 */ 123export function resetAutoModeGateCheck(): void { 124 autoModeCheckRan = false 125} 126 127export function useKickOffCheckAndDisableAutoModeIfNeeded(): void { 128 const mainLoopModel = useAppState(s => s.mainLoopModel) 129 const mainLoopModelForSession = useAppState(s => s.mainLoopModelForSession) 130 const fastMode = useAppState(s => s.fastMode) 131 const setAppState = useSetAppState() 132 const store = useAppStateStore() 133 const isFirstRunRef = useRef(true) 134 135 // Runs on mount (startup check) AND whenever the model or fast mode changes 136 // (kick-out / carousel-restore). Watching both model fields covers /model, 137 // Cmd+P picker, /config, and bridge onSetModel paths; fastMode covers 138 // /fast on|off for the tengu_auto_mode_config.disableFastMode circuit 139 // breaker. The print.ts headless paths are covered by the sync 140 // isAutoModeGateEnabled() check. 141 useEffect(() => { 142 if (getIsRemoteMode()) return 143 if (isFirstRunRef.current) { 144 isFirstRunRef.current = false 145 } else { 146 resetAutoModeGateCheck() 147 } 148 void checkAndDisableAutoModeIfNeeded( 149 store.getState().toolPermissionContext, 150 setAppState, 151 fastMode, 152 ) 153 // eslint-disable-next-line react-hooks/exhaustive-deps 154 }, [mainLoopModel, mainLoopModelForSession, fastMode]) 155}