File size: 4,839 Bytes
064bfd6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | 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<void> {
// 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<void> {
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])
}
|