From 672a03b0525e581f856fef8422c541e0755c4000 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 24 Jun 2026 16:49:34 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=A9=20fix:=20Bring=20memory=20tool=20t?= =?UTF-8?q?o=20parity=20with=20other=20ephemeral=20tools?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `memory` to the model-spec schema/type and honor `modelSpec.memory` in both ephemeral paths (load.ts, added.ts) and the frontend spec application, so admins can pre-enable Memory from a model spec exactly like webSearch/fileSearch/executeCode. - Add LAST_MEMORY_TOGGLE_ to the timestamped-storage cleanup list so stale per-conversation memory toggles are purged on startup like the others. - Hide the agent-builder Memory toggle for users who disabled memory in personalization (memories === false), mirroring the chat badge's opt-out gate, so the setting isn't shown as inert/misleading. --- .../components/SidePanel/Agents/AgentConfig.tsx | 14 ++++++++++++-- client/src/utils/__tests__/timestamps.test.ts | 13 +++++++++++++ client/src/utils/endpoints.ts | 1 + client/src/utils/timestamps.ts | 1 + packages/api/src/agents/added.ts | 2 +- packages/api/src/agents/load.ts | 2 +- packages/data-provider/src/models.ts | 2 ++ 7 files changed, 31 insertions(+), 4 deletions(-) diff --git a/client/src/components/SidePanel/Agents/AgentConfig.tsx b/client/src/components/SidePanel/Agents/AgentConfig.tsx index ed95912c7f..b5c6973c38 100644 --- a/client/src/components/SidePanel/Agents/AgentConfig.tsx +++ b/client/src/components/SidePanel/Agents/AgentConfig.tsx @@ -20,7 +20,13 @@ import { getIconKey, cn, } from '~/utils'; -import { useLocalize, useVisibleTools, useHasAccess, useHasMemoryAccess } from '~/hooks'; +import { + useLocalize, + useVisibleTools, + useHasAccess, + useHasMemoryAccess, + useAuthContext, +} from '~/hooks'; import { ToolSelectDialog, MCPToolSelectDialog } from '~/components/Tools'; import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities'; import { useListSkillsQuery, useGetAgentFiles } from '~/data-provider'; @@ -114,8 +120,12 @@ export default function AgentConfig() { permissionType: PermissionTypes.SKILLS, permission: Permissions.USE, }); + const { user } = useAuthContext(); const hasMemoryAccess = useHasMemoryAccess(); - const showMemory = hasMemoryAccess && memoryEnabled; + /** Mirror the chat memory badge's opt-out gate: a user who disabled memory in + * personalization can't use the inline tools, so the builder toggle is inert + * for them and must be hidden too. */ + const showMemory = hasMemoryAccess && memoryEnabled && user?.personalization?.memories !== false; const showSkills = hasSkillsAccess && skillsEnabled; const { data: skillsData } = useListSkillsQuery({ limit: 100 }, { enabled: showSkills }); const skillsMap = useMemo(() => { diff --git a/client/src/utils/__tests__/timestamps.test.ts b/client/src/utils/__tests__/timestamps.test.ts index 2a89c71bc2..4572fb74c7 100644 --- a/client/src/utils/__tests__/timestamps.test.ts +++ b/client/src/utils/__tests__/timestamps.test.ts @@ -132,6 +132,19 @@ describe('timestamps', () => { expect(localStorage.getItem(regularKey)).toBe('value'); }); + + it('should purge stale memory toggle entries', () => { + const key = `${LocalStorageKeys.LAST_MEMORY_TOGGLE_}convo-321`; + const oldTimestamp = Date.now() - 3 * 24 * 60 * 60 * 1000; // 3 days ago + + localStorage.setItem(key, 'true'); + localStorage.setItem(`${key}_TIMESTAMP`, oldTimestamp.toString()); + + cleanupTimestampedStorage(); + + expect(localStorage.getItem(key)).toBeNull(); + expect(localStorage.getItem(`${key}_TIMESTAMP`)).toBeNull(); + }); }); describe('migrateExistingEntries', () => { diff --git a/client/src/utils/endpoints.ts b/client/src/utils/endpoints.ts index d186a32037..06c6f0a060 100644 --- a/client/src/utils/endpoints.ts +++ b/client/src/utils/endpoints.ts @@ -368,6 +368,7 @@ export function applyModelSpecEphemeralAgent({ web_search: modelSpec.webSearch ?? false, file_search: modelSpec.fileSearch ?? false, execute_code: modelSpec.executeCode ?? false, + memory: modelSpec.memory ?? false, artifacts: modelSpec.artifacts === true ? 'default' : modelSpec.artifacts || '', }; diff --git a/client/src/utils/timestamps.ts b/client/src/utils/timestamps.ts index db114263f8..e84b8cc75c 100644 --- a/client/src/utils/timestamps.ts +++ b/client/src/utils/timestamps.ts @@ -16,6 +16,7 @@ const TIMESTAMPED_KEYS = [ LocalStorageKeys.LAST_WEB_SEARCH_TOGGLE_, LocalStorageKeys.LAST_FILE_SEARCH_TOGGLE_, LocalStorageKeys.LAST_ARTIFACTS_TOGGLE_, + LocalStorageKeys.LAST_MEMORY_TOGGLE_, LocalStorageKeys.PIN_MCP_, ]; diff --git a/packages/api/src/agents/added.ts b/packages/api/src/agents/added.ts index 6d5f024b4a..8c54c9c6e3 100644 --- a/packages/api/src/agents/added.ts +++ b/packages/api/src/agents/added.ts @@ -181,7 +181,7 @@ export async function loadAddedAgent( if (ephemeralAgent?.web_search === true || modelSpec?.webSearch === true) { tools.push(Tools.web_search); } - if (ephemeralAgent?.memory === true) { + if (ephemeralAgent?.memory === true || modelSpec?.memory === true) { tools.push(Tools.memory); } diff --git a/packages/api/src/agents/load.ts b/packages/api/src/agents/load.ts index 459015e278..769f3f4ef6 100644 --- a/packages/api/src/agents/load.ts +++ b/packages/api/src/agents/load.ts @@ -73,7 +73,7 @@ export async function loadEphemeralAgent( if (ephemeralAgent?.web_search === true || modelSpec?.webSearch === true) { tools.push(Tools.web_search); } - if (ephemeralAgent?.memory === true) { + if (ephemeralAgent?.memory === true || modelSpec?.memory === true) { tools.push(Tools.memory); } diff --git a/packages/data-provider/src/models.ts b/packages/data-provider/src/models.ts index b34d51feb3..d123f9131e 100644 --- a/packages/data-provider/src/models.ts +++ b/packages/data-provider/src/models.ts @@ -44,6 +44,7 @@ export type TModelSpec = { webSearch?: boolean; fileSearch?: boolean; executeCode?: boolean; + memory?: boolean; artifacts?: string | boolean; mcpServers?: string[]; skills?: boolean | string[]; @@ -76,6 +77,7 @@ export const tModelSpecSchema = z.object({ webSearch: z.boolean().optional(), fileSearch: z.boolean().optional(), executeCode: z.boolean().optional(), + memory: z.boolean().optional(), artifacts: z.union([z.string(), z.boolean()]).optional(), mcpServers: z.array(z.string()).optional(), skills: z.union([z.boolean(), z.array(z.string())]).optional(),