useFilePermissionDialog.ts
components/permissions/FilePermissionDialog/useFilePermissionDialog.ts
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with file-tools, shell-safety, permissions, ui-flow. It contains 213 lines, 10 detected imports, and 4 detected exports.
Important relationships
Detected exports
ToolInputUseFilePermissionDialogPropsUseFilePermissionDialogResultuseFilePermissionDialog
Keywords
Detected imports
reactsrc/state/AppState.js../../../keybindings/useKeybinding.js../../../services/analytics/index.js../../../services/analytics/metadata.js../../../utils/permissions/PermissionUpdateSchema.js../../../utils/unaryLogging.js../PermissionRequest.js./permissionOptions.js./usePermissionHandler.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import { useCallback, useMemo, useState } from 'react'
import { useAppState } from 'src/state/AppState.js'
import { useKeybindings } from '../../../keybindings/useKeybinding.js'
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../../../services/analytics/index.js'
import { sanitizeToolNameForAnalytics } from '../../../services/analytics/metadata.js'
import type { PermissionUpdate } from '../../../utils/permissions/PermissionUpdateSchema.js'
import type { CompletionType } from '../../../utils/unaryLogging.js'
import type { ToolUseConfirm } from '../PermissionRequest.js'
import {
type FileOperationType,
getFilePermissionOptions,
type PermissionOption,
type PermissionOptionWithLabel,
} from './permissionOptions.js'
import {
PERMISSION_HANDLERS,
type PermissionHandlerParams,
} from './usePermissionHandler.js'
export interface ToolInput {
[key: string]: unknown
}
export type UseFilePermissionDialogProps<T extends ToolInput> = {
filePath: string
completionType: CompletionType
languageName: string | Promise<string>
toolUseConfirm: ToolUseConfirm
onDone: () => void
onReject: () => void
parseInput: (input: unknown) => T
operationType?: FileOperationType
}
export type UseFilePermissionDialogResult<T> = {
options: PermissionOptionWithLabel[]
onChange: (option: PermissionOption, input: T, feedback?: string) => void
acceptFeedback: string
rejectFeedback: string
focusedOption: string
setFocusedOption: (option: string) => void
handleInputModeToggle: (value: string) => void
yesInputMode: boolean
noInputMode: boolean
}
/**
* Hook for handling file permission dialogs with common logic
*/
export function useFilePermissionDialog<T extends ToolInput>({
filePath,
completionType,
languageName,
toolUseConfirm,
onDone,
onReject,
parseInput,
operationType = 'write',
}: UseFilePermissionDialogProps<T>): UseFilePermissionDialogResult<T> {
const toolPermissionContext = useAppState(s => s.toolPermissionContext)
const [acceptFeedback, setAcceptFeedback] = useState('')
const [rejectFeedback, setRejectFeedback] = useState('')
const [focusedOption, setFocusedOption] = useState('yes')
const [yesInputMode, setYesInputMode] = useState(false)
const [noInputMode, setNoInputMode] = useState(false)
// Track whether user ever entered feedback mode (persists after collapse)
const [yesFeedbackModeEntered, setYesFeedbackModeEntered] = useState(false)
const [noFeedbackModeEntered, setNoFeedbackModeEntered] = useState(false)
// Generate options based on context
const options = useMemo(
() =>
getFilePermissionOptions({
filePath,
toolPermissionContext,
operationType,
onRejectFeedbackChange: setRejectFeedback,
onAcceptFeedbackChange: setAcceptFeedback,
yesInputMode,
noInputMode,
}),
[filePath, toolPermissionContext, operationType, yesInputMode, noInputMode],
)
// Handle option selection using shared handlers
const onChange = useCallback(
(option: PermissionOption, input: T, feedback?: string) => {
const params: PermissionHandlerParams = {
messageId: toolUseConfirm.assistantMessage.message.id,
path: filePath,
toolUseConfirm,
toolPermissionContext,
onDone,
onReject,
completionType,
languageName,
operationType,
}
// Override the input in toolUseConfirm to pass the parsed input
const originalOnAllow = toolUseConfirm.onAllow
toolUseConfirm.onAllow = (
_input: unknown,
permissionUpdates: PermissionUpdate[],
feedback?: string,
) => {
originalOnAllow(input, permissionUpdates, feedback)
}
const handler = PERMISSION_HANDLERS[option.type]
handler(params, {
feedback,
hasFeedback: !!feedback,
enteredFeedbackMode:
option.type === 'accept-once'
? yesFeedbackModeEntered
: noFeedbackModeEntered,
scope: option.type === 'accept-session' ? option.scope : undefined,
})
},
[
filePath,
completionType,
languageName,
toolUseConfirm,
toolPermissionContext,
onDone,
onReject,
operationType,
yesFeedbackModeEntered,
noFeedbackModeEntered,
],
)
// Handler for confirm:cycleMode - select accept-session option
const handleCycleMode = useCallback(() => {
const sessionOption = options.find(o => o.option.type === 'accept-session')
if (sessionOption) {
const parsedInput = parseInput(toolUseConfirm.input)
onChange(sessionOption.option, parsedInput)
}
}, [options, parseInput, toolUseConfirm.input, onChange])
// Register keyboard shortcut handler via keybindings system
useKeybindings(
{ 'confirm:cycleMode': handleCycleMode },
{ context: 'Confirmation' },
)
// Wrap setFocusedOption and reset input mode when navigating away
const handleFocusedOptionChange = useCallback(
(value: string) => {
// Reset input mode when navigating away, but only if no text typed
if (value !== 'yes' && yesInputMode && !acceptFeedback.trim()) {
setYesInputMode(false)
}
if (value !== 'no' && noInputMode && !rejectFeedback.trim()) {
setNoInputMode(false)
}
setFocusedOption(value)
},
[yesInputMode, noInputMode, acceptFeedback, rejectFeedback],
)
// Handle Tab key toggling input mode for Yes/No options
const handleInputModeToggle = useCallback(
(value: string) => {
const analyticsProps = {
toolName: sanitizeToolNameForAnalytics(
toolUseConfirm.tool.name,
) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
isMcp: toolUseConfirm.tool.isMcp ?? false,
}
if (value === 'yes') {
if (yesInputMode) {
setYesInputMode(false)
logEvent('tengu_accept_feedback_mode_collapsed', analyticsProps)
} else {
setYesInputMode(true)
setYesFeedbackModeEntered(true)
logEvent('tengu_accept_feedback_mode_entered', analyticsProps)
}
} else if (value === 'no') {
if (noInputMode) {
setNoInputMode(false)
logEvent('tengu_reject_feedback_mode_collapsed', analyticsProps)
} else {
setNoInputMode(true)
setNoFeedbackModeEntered(true)
logEvent('tengu_reject_feedback_mode_entered', analyticsProps)
}
}
},
[yesInputMode, noInputMode, toolUseConfirm],
)
return {
options,
onChange,
acceptFeedback,
rejectFeedback,
focusedOption,
setFocusedOption: handleFocusedOptionChange,
handleInputModeToggle,
yesInputMode,
noInputMode,
}
}