takt/src/core/piece/permission-profile-resolution.ts

89 lines
3.1 KiB
TypeScript

import type { PermissionMode } from '../models/types.js';
import type { ProviderPermissionProfiles, ProviderProfileName } from '../models/provider-profiles.js';
export interface ResolvePermissionModeInput {
movementName: string;
requiredPermissionMode?: PermissionMode;
provider?: ProviderProfileName;
projectProviderProfiles?: ProviderPermissionProfiles;
globalProviderProfiles?: ProviderPermissionProfiles;
}
export const DEFAULT_PROVIDER_PERMISSION_PROFILES: ProviderPermissionProfiles = {
claude: { defaultPermissionMode: 'edit' },
codex: { defaultPermissionMode: 'edit' },
opencode: { defaultPermissionMode: 'edit' },
mock: { defaultPermissionMode: 'edit' },
};
/**
* Resolve movement permission mode using provider profiles.
*
* Priority:
* 1. project provider_profiles.<provider>.movement_permission_overrides.<movement>
* 2. global provider_profiles.<provider>.movement_permission_overrides.<movement>
* 3. project provider_profiles.<provider>.default_permission_mode
* 4. global provider_profiles.<provider>.default_permission_mode
* 5. apply movement.required_permission_mode as minimum floor
*
* Throws when unresolved.
*/
export function resolveMovementPermissionMode(input: ResolvePermissionModeInput): PermissionMode {
if (!input.provider) {
if (input.requiredPermissionMode) {
return input.requiredPermissionMode;
}
throw new Error(
`Unable to resolve permission mode for movement "${input.movementName}": provider is required when movement.required_permission_mode is omitted.`,
);
}
const projectProfile = input.projectProviderProfiles?.[input.provider];
const globalProfile = input.globalProviderProfiles?.[input.provider];
const projectOverride = projectProfile?.movementPermissionOverrides?.[input.movementName];
if (projectOverride) {
return applyRequiredPermissionFloor(projectOverride, input.requiredPermissionMode);
}
const globalOverride = globalProfile?.movementPermissionOverrides?.[input.movementName];
if (globalOverride) {
return applyRequiredPermissionFloor(globalOverride, input.requiredPermissionMode);
}
if (projectProfile?.defaultPermissionMode) {
return applyRequiredPermissionFloor(projectProfile.defaultPermissionMode, input.requiredPermissionMode);
}
if (globalProfile?.defaultPermissionMode) {
return applyRequiredPermissionFloor(globalProfile.defaultPermissionMode, input.requiredPermissionMode);
}
if (input.requiredPermissionMode) {
return input.requiredPermissionMode;
}
throw new Error(
`Unable to resolve permission mode for movement "${input.movementName}" and provider "${input.provider}": ` +
'define provider_profiles defaults/overrides or movement.required_permission_mode.',
);
}
const PERMISSION_MODE_RANK: Record<PermissionMode, number> = {
readonly: 0,
edit: 1,
full: 2,
};
function applyRequiredPermissionFloor(
resolvedMode: PermissionMode,
requiredMode?: PermissionMode,
): PermissionMode {
if (!requiredMode) {
return resolvedMode;
}
return PERMISSION_MODE_RANK[requiredMode] > PERMISSION_MODE_RANK[resolvedMode]
? requiredMode
: resolvedMode;
}