Download src/utils/permissions/PermissionUpdate.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 11.9 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/utils/permissions/PermissionUpdate.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/utils/permissions/PermissionUpdate.ts
-
curl -L -o PermissionUpdate.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/utils/permissions/PermissionUpdate.ts
11.9 kB
| import { posix } from 'path' | |
| import type { ToolPermissionContext } from '../../Tool.js' | |
| // Types extracted to src/types/permissions.ts to break import cycles | |
| import type { | |
| AdditionalWorkingDirectory, | |
| WorkingDirectorySource, | |
| } from '../../types/permissions.js' | |
| import { logForDebugging } from '../debug.js' | |
| import type { EditableSettingSource } from '../settings/constants.js' | |
| import { | |
| getSettingsForSource, | |
| updateSettingsForSource, | |
| } from '../settings/settings.js' | |
| import { jsonStringify } from '../slowOperations.js' | |
| import { toPosixPath } from './filesystem.js' | |
| import type { PermissionRuleValue } from './PermissionRule.js' | |
| import type { | |
| PermissionUpdate, | |
| PermissionUpdateDestination, | |
| } from './PermissionUpdateSchema.js' | |
| import { | |
| permissionRuleValueFromString, | |
| permissionRuleValueToString, | |
| } from './permissionRuleParser.js' | |
| import { addPermissionRulesToSettings } from './permissionsLoader.js' | |
| // Re-export for backwards compatibility | |
| export type { AdditionalWorkingDirectory, WorkingDirectorySource } | |
| export function extractRules( | |
| updates: PermissionUpdate[] | undefined, | |
| ): PermissionRuleValue[] { | |
| if (!updates) return [] | |
| return updates.flatMap(update => { | |
| switch (update.type) { | |
| case 'addRules': | |
| return update.rules | |
| default: | |
| return [] | |
| } | |
| }) | |
| } | |
| export function hasRules(updates: PermissionUpdate[] | undefined): boolean { | |
| return extractRules(updates).length > 0 | |
| } | |
| /** | |
| * Applies a single permission update to the context and returns the updated context | |
| * @param context The current permission context | |
| * @param update The permission update to apply | |
| * @returns The updated permission context | |
| */ | |
| export function applyPermissionUpdate( | |
| context: ToolPermissionContext, | |
| update: PermissionUpdate, | |
| ): ToolPermissionContext { | |
| switch (update.type) { | |
| case 'setMode': | |
| logForDebugging( | |
| `Applying permission update: Setting mode to '${update.mode}'`, | |
| ) | |
| return { | |
| ...context, | |
| mode: update.mode, | |
| } | |
| case 'addRules': { | |
| const ruleStrings = update.rules.map(rule => | |
| permissionRuleValueToString(rule), | |
| ) | |
| logForDebugging( | |
| `Applying permission update: Adding ${update.rules.length} ${update.behavior} rule(s) to destination '${update.destination}': ${jsonStringify(ruleStrings)}`, | |
| ) | |
| // Determine which collection to update based on behavior | |
| const ruleKind = | |
| update.behavior === 'allow' | |
| ? 'alwaysAllowRules' | |
| : update.behavior === 'deny' | |
| ? 'alwaysDenyRules' | |
| : 'alwaysAskRules' | |
| return { | |
| ...context, | |
| [ruleKind]: { | |
| ...context[ruleKind], | |
| [update.destination]: [ | |
| ...(context[ruleKind][update.destination] || []), | |
| ...ruleStrings, | |
| ], | |
| }, | |
| } | |
| } | |
| case 'replaceRules': { | |
| const ruleStrings = update.rules.map(rule => | |
| permissionRuleValueToString(rule), | |
| ) | |
| logForDebugging( | |
| `Replacing all ${update.behavior} rules for destination '${update.destination}' with ${update.rules.length} rule(s): ${jsonStringify(ruleStrings)}`, | |
| ) | |
| // Determine which collection to update based on behavior | |
| const ruleKind = | |
| update.behavior === 'allow' | |
| ? 'alwaysAllowRules' | |
| : update.behavior === 'deny' | |
| ? 'alwaysDenyRules' | |
| : 'alwaysAskRules' | |
| return { | |
| ...context, | |
| [ruleKind]: { | |
| ...context[ruleKind], | |
| [update.destination]: ruleStrings, // Replace all rules for this source | |
| }, | |
| } | |
| } | |
| case 'addDirectories': { | |
| logForDebugging( | |
| `Applying permission update: Adding ${update.directories.length} director${update.directories.length === 1 ? 'y' : 'ies'} with destination '${update.destination}': ${jsonStringify(update.directories)}`, | |
| ) | |
| const newAdditionalDirs = new Map(context.additionalWorkingDirectories) | |
| for (const directory of update.directories) { | |
| newAdditionalDirs.set(directory, { | |
| path: directory, | |
| source: update.destination, | |
| }) | |
| } | |
| return { | |
| ...context, | |
| additionalWorkingDirectories: newAdditionalDirs, | |
| } | |
| } | |
| case 'removeRules': { | |
| const ruleStrings = update.rules.map(rule => | |
| permissionRuleValueToString(rule), | |
| ) | |
| logForDebugging( | |
| `Applying permission update: Removing ${update.rules.length} ${update.behavior} rule(s) from source '${update.destination}': ${jsonStringify(ruleStrings)}`, | |
| ) | |
| // Determine which collection to update based on behavior | |
| const ruleKind = | |
| update.behavior === 'allow' | |
| ? 'alwaysAllowRules' | |
| : update.behavior === 'deny' | |
| ? 'alwaysDenyRules' | |
| : 'alwaysAskRules' | |
| // Filter out the rules to be removed | |
| const existingRules = context[ruleKind][update.destination] || [] | |
| const rulesToRemove = new Set(ruleStrings) | |
| const filteredRules = existingRules.filter( | |
| rule => !rulesToRemove.has(rule), | |
| ) | |
| return { | |
| ...context, | |
| [ruleKind]: { | |
| ...context[ruleKind], | |
| [update.destination]: filteredRules, | |
| }, | |
| } | |
| } | |
| case 'removeDirectories': { | |
| logForDebugging( | |
| `Applying permission update: Removing ${update.directories.length} director${update.directories.length === 1 ? 'y' : 'ies'}: ${jsonStringify(update.directories)}`, | |
| ) | |
| const newAdditionalDirs = new Map(context.additionalWorkingDirectories) | |
| for (const directory of update.directories) { | |
| newAdditionalDirs.delete(directory) | |
| } | |
| return { | |
| ...context, | |
| additionalWorkingDirectories: newAdditionalDirs, | |
| } | |
| } | |
| default: | |
| return context | |
| } | |
| } | |
| /** | |
| * Applies multiple permission updates to the context and returns the updated context | |
| * @param context The current permission context | |
| * @param updates The permission updates to apply | |
| * @returns The updated permission context | |
| */ | |
| export function applyPermissionUpdates( | |
| context: ToolPermissionContext, | |
| updates: PermissionUpdate[], | |
| ): ToolPermissionContext { | |
| let updatedContext = context | |
| for (const update of updates) { | |
| updatedContext = applyPermissionUpdate(updatedContext, update) | |
| } | |
| return updatedContext | |
| } | |
| export function supportsPersistence( | |
| destination: PermissionUpdateDestination, | |
| ): destination is EditableSettingSource { | |
| return ( | |
| destination === 'localSettings' || | |
| destination === 'userSettings' || | |
| destination === 'projectSettings' | |
| ) | |
| } | |
| /** | |
| * Persists a permission update to the appropriate settings source | |
| * @param update The permission update to persist | |
| */ | |
| export function persistPermissionUpdate(update: PermissionUpdate): void { | |
| if (!supportsPersistence(update.destination)) return | |
| logForDebugging( | |
| `Persisting permission update: ${update.type} to source '${update.destination}'`, | |
| ) | |
| switch (update.type) { | |
| case 'addRules': { | |
| logForDebugging( | |
| `Persisting ${update.rules.length} ${update.behavior} rule(s) to ${update.destination}`, | |
| ) | |
| addPermissionRulesToSettings( | |
| { | |
| ruleValues: update.rules, | |
| ruleBehavior: update.behavior, | |
| }, | |
| update.destination, | |
| ) | |
| break | |
| } | |
| case 'addDirectories': { | |
| logForDebugging( | |
| `Persisting ${update.directories.length} director${update.directories.length === 1 ? 'y' : 'ies'} to ${update.destination}`, | |
| ) | |
| const existingSettings = getSettingsForSource(update.destination) | |
| const existingDirs = | |
| existingSettings?.permissions?.additionalDirectories || [] | |
| // Add new directories, avoiding duplicates | |
| const dirsToAdd = update.directories.filter( | |
| dir => !existingDirs.includes(dir), | |
| ) | |
| if (dirsToAdd.length > 0) { | |
| const updatedDirs = [...existingDirs, ...dirsToAdd] | |
| updateSettingsForSource(update.destination, { | |
| permissions: { | |
| additionalDirectories: updatedDirs, | |
| }, | |
| }) | |
| } | |
| break | |
| } | |
| case 'removeRules': { | |
| // Handle rule removal | |
| logForDebugging( | |
| `Removing ${update.rules.length} ${update.behavior} rule(s) from ${update.destination}`, | |
| ) | |
| const existingSettings = getSettingsForSource(update.destination) | |
| const existingPermissions = existingSettings?.permissions || {} | |
| const existingRules = existingPermissions[update.behavior] || [] | |
| // Convert rules to normalized strings for comparison | |
| // Normalize via parse→serialize roundtrip so "Bash(*)" and "Bash" match | |
| const rulesToRemove = new Set( | |
| update.rules.map(permissionRuleValueToString), | |
| ) | |
| const filteredRules = existingRules.filter(rule => { | |
| const normalized = permissionRuleValueToString( | |
| permissionRuleValueFromString(rule), | |
| ) | |
| return !rulesToRemove.has(normalized) | |
| }) | |
| updateSettingsForSource(update.destination, { | |
| permissions: { | |
| [update.behavior]: filteredRules, | |
| }, | |
| }) | |
| break | |
| } | |
| case 'removeDirectories': { | |
| logForDebugging( | |
| `Removing ${update.directories.length} director${update.directories.length === 1 ? 'y' : 'ies'} from ${update.destination}`, | |
| ) | |
| const existingSettings = getSettingsForSource(update.destination) | |
| const existingDirs = | |
| existingSettings?.permissions?.additionalDirectories || [] | |
| // Remove specified directories | |
| const dirsToRemove = new Set(update.directories) | |
| const filteredDirs = existingDirs.filter(dir => !dirsToRemove.has(dir)) | |
| updateSettingsForSource(update.destination, { | |
| permissions: { | |
| additionalDirectories: filteredDirs, | |
| }, | |
| }) | |
| break | |
| } | |
| case 'setMode': { | |
| logForDebugging( | |
| `Persisting mode '${update.mode}' to ${update.destination}`, | |
| ) | |
| updateSettingsForSource(update.destination, { | |
| permissions: { | |
| defaultMode: update.mode, | |
| }, | |
| }) | |
| break | |
| } | |
| case 'replaceRules': { | |
| logForDebugging( | |
| `Replacing all ${update.behavior} rules in ${update.destination} with ${update.rules.length} rule(s)`, | |
| ) | |
| const ruleStrings = update.rules.map(permissionRuleValueToString) | |
| updateSettingsForSource(update.destination, { | |
| permissions: { | |
| [update.behavior]: ruleStrings, | |
| }, | |
| }) | |
| break | |
| } | |
| } | |
| } | |
| /** | |
| * Persists multiple permission updates to the appropriate settings sources | |
| * Only persists updates with persistable sources | |
| * @param updates The permission updates to persist | |
| */ | |
| export function persistPermissionUpdates(updates: PermissionUpdate[]): void { | |
| for (const update of updates) { | |
| persistPermissionUpdate(update) | |
| } | |
| } | |
| /** | |
| * Creates a Read rule suggestion for a directory. | |
| * @param dirPath The directory path to create a rule for | |
| * @param destination The destination for the permission rule (defaults to 'session') | |
| * @returns A PermissionUpdate for a Read rule, or undefined for the root directory | |
| */ | |
| export function createReadRuleSuggestion( | |
| dirPath: string, | |
| destination: PermissionUpdateDestination = 'session', | |
| ): PermissionUpdate | undefined { | |
| // Convert to POSIX format for pattern matching (handles Windows internally) | |
| const pathForPattern = toPosixPath(dirPath) | |
| // Root directory is too broad to be a reasonable permission target | |
| if (pathForPattern === '/') { | |
| return undefined | |
| } | |
| // For absolute paths, prepend an extra / to create //path/** pattern | |
| const ruleContent = posix.isAbsolute(pathForPattern) | |
| ? `/${pathForPattern}/**` | |
| : `${pathForPattern}/**` | |
| return { | |
| type: 'addRules', | |
| rules: [ | |
| { | |
| toolName: 'Read', | |
| ruleContent, | |
| }, | |
| ], | |
| behavior: 'allow', | |
| destination, | |
| } | |
| } | |