diff --git a/packages/api/src/app/permissions.spec.ts b/packages/api/src/app/permissions.spec.ts index b3a6c8deb..e3816d401 100644 --- a/packages/api/src/app/permissions.spec.ts +++ b/packages/api/src/app/permissions.spec.ts @@ -1311,6 +1311,142 @@ describe('updateInterfacePermissions - permissions', () => { }); }); + it('should re-enable memory permissions when memory.disabled changes from true to false', async () => { + // Mock existing memory permissions that are disabled + mockGetRoleByName.mockResolvedValue({ + permissions: { + [PermissionTypes.MEMORIES]: { + [Permissions.USE]: false, + [Permissions.CREATE]: false, + [Permissions.READ]: false, + [Permissions.UPDATE]: false, + [Permissions.OPT_OUT]: false, + }, + // Other existing permissions + [PermissionTypes.PROMPTS]: { [Permissions.USE]: true }, + [PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true }, + }, + }); + + const config = { + interface: { + // Not explicitly configuring memories in interface + prompts: true, + bookmarks: true, + }, + memory: { + disabled: false, // Memory is explicitly enabled (changed from true to false) + agent: { + id: 'test-agent-id', + }, + personalize: true, + } as unknown as TCustomConfig['memory'], + }; + const configDefaults = { + interface: { + memories: true, + prompts: true, + bookmarks: true, + }, + } as TConfigDefaults; + const interfaceConfig = await loadDefaultInterface({ config, configDefaults }); + const appConfig = { config, interfaceConfig } as unknown as AppConfig; + + await updateInterfacePermissions({ + appConfig, + getRoleByName: mockGetRoleByName, + updateAccessPermissions: mockUpdateAccessPermissions, + }); + + // Check USER role call + const userCall = mockUpdateAccessPermissions.mock.calls.find( + (call) => call[0] === SystemRoles.USER, + ); + // Memory permissions should be re-enabled + expect(userCall[1][PermissionTypes.MEMORIES]).toEqual({ + [Permissions.USE]: true, + [Permissions.CREATE]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.MEMORIES]?.[Permissions.CREATE], + [Permissions.READ]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.MEMORIES]?.[Permissions.READ], + [Permissions.UPDATE]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.MEMORIES]?.[Permissions.UPDATE], + [Permissions.OPT_OUT]: true, // Should be true when personalize is enabled + }); + + // Check ADMIN role call + const adminCall = mockUpdateAccessPermissions.mock.calls.find( + (call) => call[0] === SystemRoles.ADMIN, + ); + expect(adminCall[1][PermissionTypes.MEMORIES]).toEqual({ + [Permissions.USE]: true, + [Permissions.CREATE]: + roleDefaults[SystemRoles.ADMIN].permissions[PermissionTypes.MEMORIES]?.[Permissions.CREATE], + [Permissions.READ]: + roleDefaults[SystemRoles.ADMIN].permissions[PermissionTypes.MEMORIES]?.[Permissions.READ], + [Permissions.UPDATE]: + roleDefaults[SystemRoles.ADMIN].permissions[PermissionTypes.MEMORIES]?.[Permissions.UPDATE], + [Permissions.OPT_OUT]: true, // Should be true when personalize is enabled + }); + + // Verify the existing role data was passed to updateAccessPermissions + expect(userCall[2]).toMatchObject({ + permissions: expect.objectContaining({ + [PermissionTypes.MEMORIES]: expect.any(Object), + }), + }); + }); + + it('should re-enable memory permissions when valid memory config exists without disabled field', async () => { + // Mock existing memory permissions that are disabled + mockGetRoleByName.mockResolvedValue({ + permissions: { + [PermissionTypes.MEMORIES]: { + [Permissions.USE]: false, + [Permissions.CREATE]: false, + [Permissions.READ]: false, + [Permissions.UPDATE]: false, + [Permissions.OPT_OUT]: false, + }, + }, + }); + + const config = { + memory: { + // No disabled field, but valid config + agent: { + id: 'test-agent-id', + provider: 'openai', + }, + personalize: false, + } as unknown as TCustomConfig['memory'], + }; + const configDefaults = { interface: {} } as TConfigDefaults; + const interfaceConfig = await loadDefaultInterface({ config, configDefaults }); + const appConfig = { config, interfaceConfig } as unknown as AppConfig; + + await updateInterfacePermissions({ + appConfig, + getRoleByName: mockGetRoleByName, + updateAccessPermissions: mockUpdateAccessPermissions, + }); + + // Check USER role call - memory should be re-enabled + const userCall = mockUpdateAccessPermissions.mock.calls.find( + (call) => call[0] === SystemRoles.USER, + ); + expect(userCall[1][PermissionTypes.MEMORIES]).toEqual({ + [Permissions.USE]: true, + [Permissions.CREATE]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.MEMORIES]?.[Permissions.CREATE], + [Permissions.READ]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.MEMORIES]?.[Permissions.READ], + [Permissions.UPDATE]: + roleDefaults[SystemRoles.USER].permissions[PermissionTypes.MEMORIES]?.[Permissions.UPDATE], + [Permissions.OPT_OUT]: undefined, // Should be undefined when personalize is false + }); + }); + it('should override existing memory permissions when memory.disabled is true', async () => { // Mock existing memory permissions that are enabled mockGetRoleByName.mockResolvedValue({ diff --git a/packages/api/src/app/permissions.ts b/packages/api/src/app/permissions.ts index ac37025a2..a039a1503 100644 --- a/packages/api/src/app/permissions.ts +++ b/packages/api/src/app/permissions.ts @@ -69,8 +69,12 @@ export async function updateInterfacePermissions({ const interfaceConfig = appConfig?.config?.interface; const memoryConfig = appConfig?.config?.memory; const memoryEnabled = isMemoryEnabled(memoryConfig); - /** Check if memory is explicitly disabled */ - const isMemoryExplicitlyDisabled = memoryConfig && !memoryEnabled; + /** Check if memory is explicitly disabled (memory.disabled === true) */ + const isMemoryExplicitlyDisabled = memoryConfig?.disabled === true; + /** Check if memory should be enabled (explicitly enabled or valid config) */ + const shouldEnableMemory = + memoryConfig?.disabled === false || + (memoryConfig && memoryEnabled && memoryConfig.disabled === undefined); /** Check if personalization is enabled (defaults to true if memory is configured and enabled) */ const isPersonalizationEnabled = memoryConfig && memoryEnabled && memoryConfig.personalize !== false; @@ -111,19 +115,24 @@ export async function updateInterfacePermissions({ const permTypeExists = existingPermissions?.[permType]; const isExplicitlyConfigured = interfaceConfig && hasExplicitConfig(interfaceConfig, permType); - const isMemoryDisabled = - permType === PermissionTypes.MEMORIES && isMemoryExplicitlyDisabled === true; + const isMemoryDisabled = permType === PermissionTypes.MEMORIES && isMemoryExplicitlyDisabled; + const isMemoryReenabling = + permType === PermissionTypes.MEMORIES && + shouldEnableMemory && + existingPermissions?.[PermissionTypes.MEMORIES]?.[Permissions.USE] === false; - // Only update if: doesn't exist OR explicitly configured - if (!permTypeExists || isExplicitlyConfigured || isMemoryDisabled) { + // Only update if: doesn't exist OR explicitly configured OR memory state change + if (!permTypeExists || isExplicitlyConfigured || isMemoryDisabled || isMemoryReenabling) { permissionsToUpdate[permType] = permissions; if (!permTypeExists) { logger.debug(`Role '${roleName}': Setting up default permissions for '${permType}'`); } else if (isExplicitlyConfigured) { logger.debug(`Role '${roleName}': Applying explicit config for '${permType}'`); } else if (isMemoryDisabled) { + logger.debug(`Role '${roleName}': Disabling memories as memory.disabled is true`); + } else if (isMemoryReenabling) { logger.debug( - `Role '${roleName}': Disabling memories as it is explicitly disabled in config`, + `Role '${roleName}': Re-enabling memories due to valid memory configuration`, ); } } else { @@ -147,13 +156,15 @@ export async function updateInterfacePermissions({ ), }, [PermissionTypes.MEMORIES]: { - [Permissions.USE]: isMemoryExplicitlyDisabled - ? false - : getPermissionValue( - loadedInterface.memories, - defaultPerms[PermissionTypes.MEMORIES]?.[Permissions.USE], - defaults.memories, - ), + [Permissions.USE]: (() => { + if (isMemoryExplicitlyDisabled) return false; + if (shouldEnableMemory) return true; + return getPermissionValue( + loadedInterface.memories, + defaultPerms[PermissionTypes.MEMORIES]?.[Permissions.USE], + defaults.memories, + ); + })(), ...(defaultPerms[PermissionTypes.MEMORIES]?.[Permissions.CREATE] !== undefined && { [Permissions.CREATE]: isMemoryExplicitlyDisabled ? false @@ -169,7 +180,9 @@ export async function updateInterfacePermissions({ ? false : defaultPerms[PermissionTypes.MEMORIES][Permissions.UPDATE], }), - [Permissions.OPT_OUT]: isPersonalizationEnabled, + [Permissions.OPT_OUT]: isMemoryExplicitlyDisabled + ? false + : isPersonalizationEnabled || undefined, }, [PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: getPermissionValue(