/ tools / BashTool / modeValidation.ts
modeValidation.ts
  1  import type { z } from 'zod/v4'
  2  import type { ToolPermissionContext } from '../../Tool.js'
  3  import { splitCommand_DEPRECATED } from '../../utils/bash/commands.js'
  4  import type { PermissionResult } from '../../utils/permissions/PermissionResult.js'
  5  import type { BashTool } from './BashTool.js'
  6  
  7  const ACCEPT_EDITS_ALLOWED_COMMANDS = [
  8    'mkdir',
  9    'touch',
 10    'rm',
 11    'rmdir',
 12    'mv',
 13    'cp',
 14    'sed',
 15  ] as const
 16  
 17  type FilesystemCommand = (typeof ACCEPT_EDITS_ALLOWED_COMMANDS)[number]
 18  
 19  function isFilesystemCommand(command: string): command is FilesystemCommand {
 20    return ACCEPT_EDITS_ALLOWED_COMMANDS.includes(command as FilesystemCommand)
 21  }
 22  
 23  function validateCommandForMode(
 24    cmd: string,
 25    toolPermissionContext: ToolPermissionContext,
 26  ): PermissionResult {
 27    const trimmedCmd = cmd.trim()
 28    const [baseCmd] = trimmedCmd.split(/\s+/)
 29  
 30    if (!baseCmd) {
 31      return {
 32        behavior: 'passthrough',
 33        message: 'Base command not found',
 34      }
 35    }
 36  
 37    // In Accept Edits mode, auto-allow filesystem operations
 38    if (
 39      toolPermissionContext.mode === 'acceptEdits' &&
 40      isFilesystemCommand(baseCmd)
 41    ) {
 42      return {
 43        behavior: 'allow',
 44        updatedInput: { command: cmd },
 45        decisionReason: {
 46          type: 'mode',
 47          mode: 'acceptEdits',
 48        },
 49      }
 50    }
 51  
 52    return {
 53      behavior: 'passthrough',
 54      message: `No mode-specific handling for '${baseCmd}' in ${toolPermissionContext.mode} mode`,
 55    }
 56  }
 57  
 58  /**
 59   * Checks if commands should be handled differently based on the current permission mode
 60   *
 61   * This is the main entry point for mode-based permission logic.
 62   * Currently handles Accept Edits mode for filesystem commands,
 63   * but designed to be extended for other modes.
 64   *
 65   * @param input - The bash command input
 66   * @param toolPermissionContext - Context containing mode and permissions
 67   * @returns
 68   * - 'allow' if the current mode permits auto-approval
 69   * - 'ask' if the command needs approval in current mode
 70   * - 'passthrough' if no mode-specific handling applies
 71   */
 72  export function checkPermissionMode(
 73    input: z.infer<typeof BashTool.inputSchema>,
 74    toolPermissionContext: ToolPermissionContext,
 75  ): PermissionResult {
 76    // Skip if in bypass mode (handled elsewhere)
 77    if (toolPermissionContext.mode === 'bypassPermissions') {
 78      return {
 79        behavior: 'passthrough',
 80        message: 'Bypass mode is handled in main permission flow',
 81      }
 82    }
 83  
 84    // Skip if in dontAsk mode (handled in main permission flow)
 85    if (toolPermissionContext.mode === 'dontAsk') {
 86      return {
 87        behavior: 'passthrough',
 88        message: 'DontAsk mode is handled in main permission flow',
 89      }
 90    }
 91  
 92    const commands = splitCommand_DEPRECATED(input.command)
 93  
 94    // Check each subcommand
 95    for (const cmd of commands) {
 96      const result = validateCommandForMode(cmd, toolPermissionContext)
 97  
 98      // If any command triggers mode-specific behavior, return that result
 99      if (result.behavior !== 'passthrough') {
100        return result
101      }
102    }
103  
104    // No mode-specific handling needed
105    return {
106      behavior: 'passthrough',
107      message: 'No mode-specific validation required',
108    }
109  }
110  
111  export function getAutoAllowedCommands(
112    mode: ToolPermissionContext['mode'],
113  ): readonly string[] {
114    return mode === 'acceptEdits' ? ACCEPT_EDITS_ALLOWED_COMMANDS : []
115  }