claude-code/components/permissions/PermissionPrompt.tsx

336 lines
36 KiB
TypeScript
Raw Normal View History

import { c as _c } from "react/compiler-runtime";
import React, { type ReactNode, useCallback, useMemo, useState } from 'react';
import { Box, Text } from '../../ink.js';
import type { KeybindingAction } from '../../keybindings/types.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 { useSetAppState } from '../../state/AppState.js';
import { type OptionWithDescription, Select } from '../CustomSelect/select.js';
export type FeedbackType = 'accept' | 'reject';
export type PermissionPromptOption<T extends string> = {
value: T;
label: ReactNode;
feedbackConfig?: {
type: FeedbackType;
placeholder?: string;
};
keybinding?: KeybindingAction;
};
export type ToolAnalyticsContext = {
toolName: string;
isMcp: boolean;
};
export type PermissionPromptProps<T extends string> = {
options: PermissionPromptOption<T>[];
onSelect: (value: T, feedback?: string) => void;
onCancel?: () => void;
question?: string | ReactNode;
toolAnalyticsContext?: ToolAnalyticsContext;
};
const DEFAULT_PLACEHOLDERS: Record<FeedbackType, string> = {
accept: 'tell Claude what to do next',
reject: 'tell Claude what to do differently'
};
/**
* Shared component for permission prompts with optional feedback input.
*
* Handles:
* - "Do you want to proceed?" question with optional Tab hint
* - Feature flag check for feedback capability
* - Input mode toggling (Tab to expand feedback input)
* - Analytics events for feedback interactions
* - Transforming options to Select-compatible format
*/
export function PermissionPrompt(t0) {
const $ = _c(54);
const {
options,
onSelect,
onCancel,
question: t1,
toolAnalyticsContext
} = t0;
const question = t1 === undefined ? "Do you want to proceed?" : t1;
const setAppState = useSetAppState();
const [acceptFeedback, setAcceptFeedback] = useState("");
const [rejectFeedback, setRejectFeedback] = useState("");
const [acceptInputMode, setAcceptInputMode] = useState(false);
const [rejectInputMode, setRejectInputMode] = useState(false);
const [focusedValue, setFocusedValue] = useState(null);
const [acceptFeedbackModeEntered, setAcceptFeedbackModeEntered] = useState(false);
const [rejectFeedbackModeEntered, setRejectFeedbackModeEntered] = useState(false);
let t2;
if ($[0] !== focusedValue || $[1] !== options) {
let t3;
if ($[3] !== focusedValue) {
t3 = opt => opt.value === focusedValue;
$[3] = focusedValue;
$[4] = t3;
} else {
t3 = $[4];
}
t2 = options.find(t3);
$[0] = focusedValue;
$[1] = options;
$[2] = t2;
} else {
t2 = $[2];
}
const focusedOption = t2;
const focusedFeedbackType = focusedOption?.feedbackConfig?.type;
const showTabHint = focusedFeedbackType === "accept" && !acceptInputMode || focusedFeedbackType === "reject" && !rejectInputMode;
let t3;
if ($[5] !== acceptInputMode || $[6] !== options || $[7] !== rejectInputMode) {
let t4;
if ($[9] !== acceptInputMode || $[10] !== rejectInputMode) {
t4 = opt_0 => {
const {
value,
label,
feedbackConfig
} = opt_0;
if (!feedbackConfig) {
return {
label,
value
};
}
const {
type,
placeholder
} = feedbackConfig;
const isInputMode = type === "accept" ? acceptInputMode : rejectInputMode;
const onChange = type === "accept" ? setAcceptFeedback : setRejectFeedback;
const defaultPlaceholder = DEFAULT_PLACEHOLDERS[type];
if (isInputMode) {
return {
type: "input" as const,
label,
value,
placeholder: placeholder ?? defaultPlaceholder,
onChange,
allowEmptySubmitToCancel: true
};
}
return {
label,
value
};
};
$[9] = acceptInputMode;
$[10] = rejectInputMode;
$[11] = t4;
} else {
t4 = $[11];
}
t3 = options.map(t4);
$[5] = acceptInputMode;
$[6] = options;
$[7] = rejectInputMode;
$[8] = t3;
} else {
t3 = $[8];
}
const selectOptions = t3;
let t4;
if ($[12] !== acceptInputMode || $[13] !== options || $[14] !== rejectInputMode || $[15] !== toolAnalyticsContext?.isMcp || $[16] !== toolAnalyticsContext?.toolName) {
t4 = value_0 => {
const option = options.find(opt_1 => opt_1.value === value_0);
if (!option?.feedbackConfig) {
return;
}
const {
type: type_0
} = option.feedbackConfig;
const analyticsProps = {
toolName: toolAnalyticsContext?.toolName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
isMcp: toolAnalyticsContext?.isMcp ?? false
};
if (type_0 === "accept") {
if (acceptInputMode) {
setAcceptInputMode(false);
logEvent("tengu_accept_feedback_mode_collapsed", analyticsProps);
} else {
setAcceptInputMode(true);
setAcceptFeedbackModeEntered(true);
logEvent("tengu_accept_feedback_mode_entered", analyticsProps);
}
} else {
if (type_0 === "reject") {
if (rejectInputMode) {
setRejectInputMode(false);
logEvent("tengu_reject_feedback_mode_collapsed", analyticsProps);
} else {
setRejectInputMode(true);
setRejectFeedbackModeEntered(true);
logEvent("tengu_reject_feedback_mode_entered", analyticsProps);
}
}
}
};
$[12] = acceptInputMode;
$[13] = options;
$[14] = rejectInputMode;
$[15] = toolAnalyticsContext?.isMcp;
$[16] = toolAnalyticsContext?.toolName;
$[17] = t4;
} else {
t4 = $[17];
}
const handleInputModeToggle = t4;
let t5;
if ($[18] !== acceptFeedback || $[19] !== acceptFeedbackModeEntered || $[20] !== onSelect || $[21] !== options || $[22] !== rejectFeedback || $[23] !== rejectFeedbackModeEntered || $[24] !== toolAnalyticsContext?.isMcp || $[25] !== toolAnalyticsContext?.toolName) {
t5 = value_1 => {
const option_0 = options.find(opt_2 => opt_2.value === value_1);
if (!option_0) {
return;
}
let feedback;
if (option_0.feedbackConfig) {
const rawFeedback = option_0.feedbackConfig.type === "accept" ? acceptFeedback : rejectFeedback;
const trimmedFeedback = rawFeedback.trim();
if (trimmedFeedback) {
feedback = trimmedFeedback;
}
const analyticsProps_0 = {
toolName: toolAnalyticsContext?.toolName as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
isMcp: toolAnalyticsContext?.isMcp ?? false,
has_instructions: !!trimmedFeedback,
instructions_length: trimmedFeedback?.length ?? 0,
entered_feedback_mode: option_0.feedbackConfig.type === "accept" ? acceptFeedbackModeEntered : rejectFeedbackModeEntered
};
if (option_0.feedbackConfig.type === "accept") {
logEvent("tengu_accept_submitted", analyticsProps_0);
} else {
if (option_0.feedbackConfig.type === "reject") {
logEvent("tengu_reject_submitted", analyticsProps_0);
}
}
}
onSelect(value_1, feedback);
};
$[18] = acceptFeedback;
$[19] = acceptFeedbackModeEntered;
$[20] = onSelect;
$[21] = options;
$[22] = rejectFeedback;
$[23] = rejectFeedbackModeEntered;
$[24] = toolAnalyticsContext?.isMcp;
$[25] = toolAnalyticsContext?.toolName;
$[26] = t5;
} else {
t5 = $[26];
}
const handleSelect = t5;
let handlers;
if ($[27] !== handleSelect || $[28] !== options) {
handlers = {};
for (const opt_3 of options) {
if (opt_3.keybinding) {
handlers[opt_3.keybinding] = () => handleSelect(opt_3.value);
}
}
$[27] = handleSelect;
$[28] = options;
$[29] = handlers;
} else {
handlers = $[29];
}
const keybindingHandlers = handlers;
let t6;
if ($[30] === Symbol.for("react.memo_cache_sentinel")) {
t6 = {
context: "Confirmation"
};
$[30] = t6;
} else {
t6 = $[30];
}
useKeybindings(keybindingHandlers, t6);
let t7;
if ($[31] !== onCancel || $[32] !== setAppState) {
t7 = () => {
logEvent("tengu_permission_request_escape", {});
setAppState(_temp);
onCancel?.();
};
$[31] = onCancel;
$[32] = setAppState;
$[33] = t7;
} else {
t7 = $[33];
}
const handleCancel = t7;
let t8;
if ($[34] !== question) {
t8 = typeof question === "string" ? <Text>{question}</Text> : question;
$[34] = question;
$[35] = t8;
} else {
t8 = $[35];
}
let t9;
if ($[36] !== acceptFeedback || $[37] !== acceptInputMode || $[38] !== options || $[39] !== rejectFeedback || $[40] !== rejectInputMode) {
t9 = value_2 => {
const newOption = options.find(opt_4 => opt_4.value === value_2);
if (newOption?.feedbackConfig?.type !== "accept" && acceptInputMode && !acceptFeedback.trim()) {
setAcceptInputMode(false);
}
if (newOption?.feedbackConfig?.type !== "reject" && rejectInputMode && !rejectFeedback.trim()) {
setRejectInputMode(false);
}
setFocusedValue(value_2);
};
$[36] = acceptFeedback;
$[37] = acceptInputMode;
$[38] = options;
$[39] = rejectFeedback;
$[40] = rejectInputMode;
$[41] = t9;
} else {
t9 = $[41];
}
let t10;
if ($[42] !== handleCancel || $[43] !== handleInputModeToggle || $[44] !== handleSelect || $[45] !== selectOptions || $[46] !== t9) {
t10 = <Select options={selectOptions} inlineDescriptions={true} onChange={handleSelect} onCancel={handleCancel} onFocus={t9} onInputModeToggle={handleInputModeToggle} />;
$[42] = handleCancel;
$[43] = handleInputModeToggle;
$[44] = handleSelect;
$[45] = selectOptions;
$[46] = t9;
$[47] = t10;
} else {
t10 = $[47];
}
const t11 = showTabHint && " \xB7 Tab to amend";
let t12;
if ($[48] !== t11) {
t12 = <Box marginTop={1}><Text dimColor={true}>Esc to cancel{t11}</Text></Box>;
$[48] = t11;
$[49] = t12;
} else {
t12 = $[49];
}
let t13;
if ($[50] !== t10 || $[51] !== t12 || $[52] !== t8) {
t13 = <Box flexDirection="column">{t8}{t10}{t12}</Box>;
$[50] = t10;
$[51] = t12;
$[52] = t8;
$[53] = t13;
} else {
t13 = $[53];
}
return t13;
}
function _temp(prev) {
return {
...prev,
attribution: {
...prev.attribution,
escapeCount: prev.attribution.escapeCount + 1
}
};
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlJlYWN0Tm9kZSIsInVzZUNhbGxiYWNrIiwidXNlTWVtbyIsInVzZVN0YXRlIiwiQm94IiwiVGV4dCIsIktleWJpbmRpbmdBY3Rpb24iLCJ1c2VLZXliaW5kaW5ncyIsIkFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMiLCJsb2dFdmVudCIsInVzZVNldEFwcFN0YXRlIiwiT3B0aW9uV2l0aERlc2NyaXB0aW9uIiwiU2VsZWN0IiwiRmVlZGJhY2tUeXBlIiwiUGVybWlzc2lvblByb21wdE9wdGlvbiIsInZhbHVlIiwiVCIsImxhYmVsIiwiZmVlZGJhY2tDb25maWciLCJ0eXBlIiwicGxhY2Vob2xkZXIiLCJrZXliaW5kaW5nIiwiVG9vbEFuYWx5dGljc0NvbnRleHQiLCJ0b29sTmFtZSIsImlzTWNwIiwiUGVybWlzc2lvblByb21wdFByb3BzIiwib3B0aW9ucyIsIm9uU2VsZWN0IiwiZmVlZGJhY2siLCJvbkNhbmNlbCIsInF1ZXN0aW9uIiwidG9vbEFuYWx5dGljc0NvbnRleHQiLCJERUZBVUxUX1BMQUNFSE9MREVSUyIsIlJlY29yZCIsImFjY2VwdCIsInJlamVjdCIsIlBlcm1pc3Npb25Qcm9tcHQiLCJ0MCIsIiQiLCJfYyIsInQxIiwidW5kZWZpbmVkIiwic2V0QXBwU3RhdGUiLCJhY2NlcHRGZWVkYmFjayIsInNldEFjY2VwdEZlZWRiYWNrIiwicmVqZWN0RmVlZGJhY2siLCJzZXRSZWplY3RGZWVkYmFjayIsImFjY2VwdElucHV0TW9kZSIsInNldEFjY2VwdElucHV0TW9kZSIsInJlamVjdElucHV0TW9kZSIsInNldFJlamVjdElucHV0TW9kZSIsImZvY3VzZWRWYWx1ZSIsInNldEZvY3VzZWRWYWx1ZSIsImFjY2VwdEZlZWRiYWNrTW9kZUVudGVyZWQiLCJzZXRBY2NlcHRGZWVkYmFja01vZGVFbnRlcmVkIiwicmVqZWN0RmVlZGJhY2tNb2RlRW50ZXJlZCIsInNldFJlamVjdEZlZWRiYWNrTW9kZUVudGVyZWQiLCJ0MiIsInQzIiwib3B0IiwiZmluZCIsImZvY3VzZWRPcHRpb24iLCJmb2N1c2VkRmVlZGJhY2tUeXBlIiwic2hvd1RhYkhpbnQiLCJ0NCIsIm9wdF8wIiwiaXNJbnB1dE1vZGUiLCJvbkNoYW5nZSIsImRlZmF1bHRQbGFjZWhvbGRlciIsImNvbnN0IiwiYWxsb3dFbXB0eVN1Ym1pdFRvQ2FuY2VsIiwibWFwIiwic2VsZWN0T3B0aW9ucyIsInZhbHVlXzAiLCJvcHRpb24iLCJvcHRfMSIsInR5cGVfMCIsImFuYWx5dGljc1Byb3BzIiwiaGFuZGxlSW5wdXRNb2RlVG9nZ2xlIiwidDUiLCJ2YWx1ZV8xIiwib3B0aW9uXzAiLCJvcHRfMiIsInJhd0ZlZWRiYWNrIiwidHJpbW1lZEZlZWRiYWNrIiwidHJpbSIsImFuYWx5dGljc1Byb3BzXzAiLCJoYXNfaW5zdHJ1Y3Rpb25zIiwiaW5zdHJ1Y3Rpb25zX2xlbmd0aCIsImxlbmd0aCIsImVudGVyZWRfZmVlZGJhY2tfbW9kZSIsImhhbmRsZVNlbGVjdCIsImhhbmRsZXJzIiwib3B0XzMiLCJrZXliaW5kaW5nSGFuZGxlcnMiLCJ0NiIsIlN5bWJvbCIsImZvciIsImNvbnRleHQiLCJ0NyIsIl90ZW1wIiwiaGFuZGxlQ2FuY2VsIiwidDgiLCJ0OSIsInZhbHVlXzIiLCJuZXdPcHRpb24iLCJvcHRfNCIsInQxMCIsInQxMSIsInQxMiIsInQxMyIsInByZXYiLCJhdHRyaWJ1dGlvbiIsImVzY2FwZUNvdW50Il0sInNvdXJjZXMiOlsiUGVybWlzc2lvblByb21wdC50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFJlYWN0LCB7IHR5cGUgUmVhY3ROb2RlLCB1c2VDYWxsYmFjaywgdXNlTWVtbywgdXNlU3RhdGUgfSBmcm9tICdyZWFjdCdcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB0eXBlIHsgS2V5YmluZGluZ0FjdGlvbiB9IGZyb20gJy4uLy4uL2tleWJpbmRpbmdzL3R5cGVzLmpzJ1xuaW1wb3J0IHsgdXNlS2V5YmluZGluZ3MgfSBmcm9tICcuLi8uLi9rZXliaW5kaW5ncy91c2VLZXliaW5kaW5nLmpzJ1xuaW1wb3J0IHtcbiAgdHlwZSBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICBsb2dFdmVudCxcbn0gZnJvbSAnLi4vLi4vc2VydmljZXMvYW5hbHl0aWNzL2luZGV4LmpzJ1xuaW1wb3J0IHsgdXNlU2V0QXBwU3RhdGUgfSBmcm9tICcuLi8uLi9zdGF0ZS9BcHBTdGF0ZS5qcydcbmltcG9ydCB7IHR5cGUgT3B0aW9uV2l0aERlc2NyaXB0aW9uLCBTZWxlY3QgfSBmcm9tICcuLi9DdXN0b21TZWxlY3Qvc2VsZWN0LmpzJ1xuXG5leHBvcnQgdHlwZSBGZWVkYmFja1R5cGUgPSAnYWNjZXB0JyB8ICdyZWplY3QnXG5cbmV4cG9ydCB0eXBlIFBlcm1pc3Npb25Qcm9tcHRPcHRpb248VCBleHRlbmRzIHN0cmluZz4gPSB7XG4gIHZhbHVlOiBUXG4gIGxhYmVsOiBSZWFjdE5vZGVcbiAgZmVlZGJhY2tDb25maWc/OiB7XG4gICAgdHlwZTogRmVlZGJhY2tUeXBlXG4gICAgcGxhY2Vob2xkZXI/OiBzdHJpbmdcbiAgfVxuICBrZXliaW5kaW5nPzogS2V5YmluZGluZ0FjdGlvblxufVxuXG5leHBvcnQgdHlwZSBUb29sQW5hbHl0aWNzQ29udGV4dCA9IHtcbiAgdG9vbE5hbWU6IHN0cmluZ1xuICBpc01jcDogYm9vbGVhblxufVxuXG5leHBvcnQgdHlwZSBQZXJtaXNzaW9uUHJvbXB0UHJvcHM8VCBleHRlbmRzIHN0cmluZz4gPSB7XG4gIG9wdGlvbnM6IFBlcm1pc3Npb25Qcm9tcHRPcHRpb248VD5bXVxuICBvblNlbGVjdDogKHZhbHVlOiBULCBmZWVkYmFjaz86IHN0cmluZykgPT4gdm9pZFxuICBvbkNhbmNlbD86ICgpID0+IHZvaWRcbiAgcXVlc3Rpb24/OiBzdHJpbmcgfCBSZWFjdE5vZGVcbiAgdG9vbEFuYWx5dGljc0NvbnRleHQ/OiBUb29sQW5hbHl0aWNzQ29udGV4dFxufVxuXG5jb25zdCBERUZBVUxUX1BMQUNFSE9MREVSUzogUmVjb3JkPEZlZWRiYWNrVHlwZSwgc3RyaW5nPiA9IHtcbiAgYWNjZXB0OiAndGVsbCBDbGF1ZGUgd2hhdCB0byBkbyBuZXh0JyxcbiAgcmVqZWN0OiAndGVsbCBDbGF1ZGUgd2hhdCB0byBkbyBkaWZmZXJlbnRseScsXG59XG5cbi8qKlxuICogU2hhcmVkIGNvbXBvbmVudCBmb3IgcGVybWlzc2lvbiBwcm9tcHRzIHdpdGggb3B0aW9uYWwgZmVlZGJhY2sgaW5wdXQuXG4gKlxuICogSGFuZGxlczpcbiAqIC0gXCJEbyB5b3Ugd2FudCB0byB