/** * Pure permission type definitions extracted to break import cycles. * * This file contains only type definitions and constants with no runtime dependencies. * Implementation files remain in src/utils/permissions/ but can now import from here * to avoid circular dependencies. */ import { feature } from 'bun:bundle' import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs' // ============================================================================ // Permission Modes // ============================================================================ export const EXTERNAL_PERMISSION_MODES = [ 'acceptEdits', 'bypassPermissions', 'default', 'dontAsk', 'plan', ] as const export type ExternalPermissionMode = (typeof EXTERNAL_PERMISSION_MODES)[number] // Exhaustive mode union for typechecking. The user-addressable runtime set // is INTERNAL_PERMISSION_MODES below. export type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble' export type PermissionMode = InternalPermissionMode // Runtime validation set: modes that are user-addressable (settings.json // defaultMode, --permission-mode CLI flag, conversation recovery). export const INTERNAL_PERMISSION_MODES = [ ...EXTERNAL_PERMISSION_MODES, ...(feature('TRANSCRIPT_CLASSIFIER') ? (['auto'] as const) : ([] as const)), ] as const satisfies readonly PermissionMode[] export const PERMISSION_MODES = INTERNAL_PERMISSION_MODES // ============================================================================ // Permission Behaviors // ============================================================================ export type PermissionBehavior = 'allow' | 'deny' | 'ask' // ============================================================================ // Permission Rules // ============================================================================ /** * Where a permission rule originated from. * Includes all SettingSource values plus additional rule-specific sources. */ export type PermissionRuleSource = | 'userSettings' | 'projectSettings' | 'localSettings' | 'flagSettings' | 'policySettings' | 'cliArg' | 'command' | 'session' /** * The value of a permission rule - specifies which tool and optional content */ export type PermissionRuleValue = { toolName: string ruleContent?: string } /** * A permission rule with its source and behavior */ export type PermissionRule = { source: PermissionRuleSource ruleBehavior: PermissionBehavior ruleValue: PermissionRuleValue } // ============================================================================ // Permission Updates // ============================================================================ /** * Where a permission update should be persisted */ export type PermissionUpdateDestination = | 'userSettings' | 'projectSettings' | 'localSettings' | 'session' | 'cliArg' /** * Update operations for permission configuration */ export type PermissionUpdate = | { type: 'addRules' destination: PermissionUpdateDestination rules: PermissionRuleValue[] behavior: PermissionBehavior } | { type: 'replaceRules' destination: PermissionUpdateDestination rules: PermissionRuleValue[] behavior: PermissionBehavior } | { type: 'removeRules' destination: PermissionUpdateDestination rules: PermissionRuleValue[] behavior: PermissionBehavior } | { type: 'setMode' destination: PermissionUpdateDestination mode: ExternalPermissionMode } | { type: 'addDirectories' destination: PermissionUpdateDestination directories: string[] } | { type: 'removeDirectories' destination: PermissionUpdateDestination directories: string[] } /** * Source of an additional working directory permission. * Note: This is currently the same as PermissionRuleSource but kept as a * separate type for semantic clarity and potential future divergence. */ export type WorkingDirectorySource = PermissionRuleSource /** * An additional directory included in permission scope */ export type AdditionalWorkingDirectory = { path: string source: WorkingDirectorySource } // ============================================================================ // Permission Decisions & Results // ============================================================================ /** * Minimal command shape for permission metadata. * This is intentionally a subset of the full Command type to avoid import cycles. * Only includes properties needed by permission-related components. */ export type PermissionCommandMetadata = { name: string description?: string // Allow additional properties for forward compatibility [key: string]: unknown } /** * Metadata attached to permission decisions */ export type PermissionMetadata = | { command: PermissionCommandMetadata } | undefined /** * Result when permission is granted */ export type PermissionAllowDecision< Input extends { [key: string]: unknown } = { [key: string]: unknown }, > = { behavior: 'allow' updatedInput?: Input userModified?: boolean decisionReason?: PermissionDecisionReason toolUseID?: string acceptFeedback?: string contentBlocks?: ContentBlockParam[] } /** * Metadata for a pending classifier check that will run asynchronously. * Used to enable non-blocking allow classifier evaluation. */ export type PendingClassifierCheck = { command: string cwd: string descriptions: string[] } /** * Result when user should be prompted */ export type PermissionAskDecision< Input extends { [key: string]: unknown } = { [key: string]: unknown }, > = { behavior: 'ask' message: string updatedInput?: Input decisionReason?: PermissionDecisionReason suggestions?: PermissionUpdate[] blockedPath?: string metadata?: PermissionMetadata /** * If true, this ask decision was triggered by a bashCommandIsSafe_DEPRECATED security check * for patterns that splitCommand_DEPRECATED could misparse (e.g. line continuations, shell-quote * transformations). Used by bashToolHasPermission to block early before splitCommand_DEPRECATED * transforms the command. Not set for simple newline compound commands. */ isBashSecurityCheckForMisparsing?: boolean /** * If set, an allow classifier check should be run asynchronously. * The classifier may auto-approve the permission before the user responds. */ pendingClassifierCheck?: PendingClassifierCheck /** * Optional content blocks (e.g., images) to include alongside the rejection * message in the tool result. Used when users paste images as feedback. */ contentBlocks?: ContentBlockParam[] } /** * Result when permission is denied */ export type PermissionDenyDecision = { behavior: 'deny' message: string decisionReason: PermissionDecisionReason toolUseID?: string } /** * A permission decision - allow, ask, or deny */ export type PermissionDecision< Input extends { [key: string]: unknown } = { [key: string]: unknown }, > = | PermissionAllowDecision | PermissionAskDecision | PermissionDenyDecision /** * Permission result with additional passthrough option */ export type PermissionResult< Input extends { [key: string]: unknown } = { [key: string]: unknown }, > = | PermissionDecision | { behavior: 'passthrough' message: string decisionReason?: PermissionDecision['decisionReason'] suggestions?: PermissionUpdate[] blockedPath?: string /** * If set, an allow classifier check should be run asynchronously. * The classifier may auto-approve the permission before the user responds. */ pendingClassifierCheck?: PendingClassifierCheck } /** * Explanation of why a permission decision was made */ export type PermissionDecisionReason = | { type: 'rule' rule: PermissionRule } | { type: 'mode' mode: PermissionMode } | { type: 'subcommandResults' reasons: Map } | { type: 'permissionPromptTool' permissionPromptToolName: string toolResult: unknown } | { type: 'hook' hookName: string hookSource?: string reason?: string } | { type: 'asyncAgent' reason: string } | { type: 'sandboxOverride' reason: 'excludedCommand' | 'dangerouslyDisableSandbox' } | { type: 'classifier' classifier: string reason: string } | { type: 'workingDir' reason: string } | { type: 'safetyCheck' reason: string // When true, auto mode lets the classifier evaluate this instead of // forcing a prompt. True for sensitive-file paths (.claude/, .git/, // shell configs) — the classifier can see context and decide. False // for Windows path bypass attempts and cross-machine bridge messages. classifierApprovable: boolean } | { type: 'other' reason: string } // ============================================================================ // Bash Classifier Types // ============================================================================ export type ClassifierResult = { matches: boolean matchedDescription?: string confidence: 'high' | 'medium' | 'low' reason: string } export type ClassifierBehavior = 'deny' | 'ask' | 'allow' export type ClassifierUsage = { inputTokens: number outputTokens: number cacheReadInputTokens: number cacheCreationInputTokens: number } export type YoloClassifierResult = { thinking?: string shouldBlock: boolean reason: string unavailable?: boolean /** * API returned "prompt is too long" — the classifier transcript exceeded * the context window. Deterministic (same transcript → same error), so * callers should fall back to normal prompting rather than retry/fail-closed. */ transcriptTooLong?: boolean /** The model used for this classifier call */ model: string /** Token usage from the classifier API call (for overhead telemetry) */ usage?: ClassifierUsage /** Duration of the classifier API call in ms */ durationMs?: number /** Character lengths of the prompt components sent to the classifier */ promptLengths?: { systemPrompt: number toolCalls: number userPrompts: number } /** Path where error prompts were dumped (only set when unavailable due to API error) */ errorDumpPath?: string /** Which classifier stage produced the final decision (2-stage XML only) */ stage?: 'fast' | 'thinking' /** Token usage from stage 1 (fast) when stage 2 was also run */ stage1Usage?: ClassifierUsage /** Duration of stage 1 in ms when stage 2 was also run */ stage1DurationMs?: number /** * API request_id (req_xxx) for stage 1. Enables joining to server-side * api_usage logs for cache-miss / routing attribution. Also used for the * legacy 1-stage (tool_use) classifier — the single request goes here. */ stage1RequestId?: string /** * API message id (msg_xxx) for stage 1. Enables joining the * tengu_auto_mode_decision analytics event to the classifier's actual * prompt/completion in post-analysis. */ stage1MsgId?: string /** Token usage from stage 2 (thinking) when stage 2 was run */ stage2Usage?: ClassifierUsage /** Duration of stage 2 in ms when stage 2 was run */ stage2DurationMs?: number /** API request_id for stage 2 (set whenever stage 2 ran) */ stage2RequestId?: string /** API message id (msg_xxx) for stage 2 (set whenever stage 2 ran) */ stage2MsgId?: string } // ============================================================================ // Permission Explainer Types // ============================================================================ export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' export type PermissionExplanation = { riskLevel: RiskLevel explanation: string reasoning: string risk: string } // ============================================================================ // Tool Permission Context // ============================================================================ /** * Mapping of permission rules by their source */ export type ToolPermissionRulesBySource = { [T in PermissionRuleSource]?: string[] } /** * Context needed for permission checking in tools * Note: Uses a simplified DeepImmutable approximation for this types-only file */ export type ToolPermissionContext = { readonly mode: PermissionMode readonly additionalWorkingDirectories: ReadonlyMap< string, AdditionalWorkingDirectory > readonly alwaysAllowRules: ToolPermissionRulesBySource readonly alwaysDenyRules: ToolPermissionRulesBySource readonly alwaysAskRules: ToolPermissionRulesBySource readonly isBypassPermissionsModeAvailable: boolean readonly strippedDangerousRules?: ToolPermissionRulesBySource readonly shouldAvoidPermissionPrompts?: boolean readonly awaitAutomatedChecksBeforeDialog?: boolean readonly prePlanMode?: PermissionMode }