fix(cli): hide /memory add subcommand when memoryV2 is enabled (#26605)

This commit is contained in:
Sandy Tao
2026-05-07 13:48:12 -07:00
committed by GitHub
parent 3a0b19d900
commit 16e345831b
12 changed files with 973 additions and 574 deletions
@@ -11,6 +11,7 @@ import { createMockCommandContext } from '../../test-utils/mockCommandContext.js
import { MessageType } from '../types.js';
import type { LoadedSettings } from '../../config/settings.js';
import {
type Config,
refreshMemory,
refreshServerHierarchicalMemory,
SimpleExtensionLoader,
@@ -61,10 +62,17 @@ const mockRefreshServerHierarchicalMemory =
describe('memoryCommand', () => {
let mockContext: CommandContext;
const buildMemoryCommand = (isMemoryV2 = false): SlashCommand => {
const config: Pick<Config, 'isMemoryV2Enabled'> = {
isMemoryV2Enabled: () => isMemoryV2,
};
return memoryCommand(config as Config);
};
const getSubCommand = (
name: 'show' | 'add' | 'reload' | 'list',
): SlashCommand => {
const subCommand = memoryCommand.subCommands?.find(
const subCommand = buildMemoryCommand().subCommands?.find(
(cmd) => cmd.name === name,
);
if (!subCommand) {
@@ -73,6 +81,26 @@ describe('memoryCommand', () => {
return subCommand;
};
describe('Memory v2', () => {
it('omits the /memory add subcommand when memoryV2 is enabled', () => {
const command = buildMemoryCommand(true);
const names = command.subCommands?.map((cmd) => cmd.name) ?? [];
expect(names).not.toContain('add');
});
it('includes the /memory add subcommand by default', () => {
const command = buildMemoryCommand(false);
const names = command.subCommands?.map((cmd) => cmd.name) ?? [];
expect(names).toContain('add');
});
it('includes the /memory add subcommand when no config is provided', () => {
const command = memoryCommand(null);
const names = command.subCommands?.map((cmd) => cmd.name) ?? [];
expect(names).toContain('add');
});
});
describe('/memory show', () => {
let showCommand: SlashCommand;
let mockGetUserMemory: Mock;
@@ -462,7 +490,7 @@ describe('memoryCommand', () => {
let inboxCommand: SlashCommand;
beforeEach(() => {
inboxCommand = memoryCommand.subCommands!.find(
inboxCommand = buildMemoryCommand().subCommands!.find(
(cmd) => cmd.name === 'inbox',
)!;
expect(inboxCommand).toBeDefined();
+165 -146
View File
@@ -7,6 +7,7 @@
import React from 'react';
import {
addMemory,
type Config,
listMemoryFiles,
refreshMemory,
showMemory,
@@ -20,155 +21,173 @@ import {
} from './types.js';
import { InboxDialog } from '../components/InboxDialog.js';
export const memoryCommand: SlashCommand = {
name: 'memory',
description: 'Commands for interacting with memory',
const showSubCommand: SlashCommand = {
name: 'show',
description: 'Show the current memory contents',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context) => {
const config = context.services.agentContext?.config;
if (!config) return;
const result = showMemory(config);
context.ui.addItem(
{
type: MessageType.INFO,
text: result.content,
},
Date.now(),
);
},
};
const addSubCommand: SlashCommand = {
name: 'add',
description: 'Add content to the memory',
kind: CommandKind.BUILT_IN,
autoExecute: false,
subCommands: [
{
name: 'show',
description: 'Show the current memory contents',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context) => {
const config = context.services.agentContext?.config;
if (!config) return;
const result = showMemory(config);
action: (context, args): SlashCommandActionReturn | void => {
const result = addMemory(args);
context.ui.addItem(
{
type: MessageType.INFO,
text: result.content,
},
Date.now(),
);
if (result.type === 'message') {
return result;
}
context.ui.addItem(
{
type: MessageType.INFO,
text: `Attempting to save to memory: "${args.trim()}"`,
},
},
{
name: 'add',
description: 'Add content to the memory',
kind: CommandKind.BUILT_IN,
autoExecute: false,
action: (context, args): SlashCommandActionReturn | void => {
const result = addMemory(args);
Date.now(),
);
if (result.type === 'message') {
return result;
}
context.ui.addItem(
{
type: MessageType.INFO,
text: `Attempting to save to memory: "${args.trim()}"`,
},
Date.now(),
);
return result;
},
},
{
name: 'reload',
altNames: ['refresh'],
description: 'Reload the memory from the source',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context) => {
context.ui.addItem(
{
type: MessageType.INFO,
text: 'Reloading memory from source files...',
},
Date.now(),
);
try {
const config = context.services.agentContext?.config;
if (config) {
const result = await refreshMemory(config);
context.ui.addItem(
{
type: MessageType.INFO,
text: result.content,
},
Date.now(),
);
}
} catch (error) {
context.ui.addItem(
{
type: MessageType.ERROR,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
text: `Error reloading memory: ${(error as Error).message}`,
},
Date.now(),
);
}
},
},
{
name: 'list',
description: 'Lists the paths of the GEMINI.md files in use',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context) => {
const config = context.services.agentContext?.config;
if (!config) return;
const result = listMemoryFiles(config);
context.ui.addItem(
{
type: MessageType.INFO,
text: result.content,
},
Date.now(),
);
},
},
{
name: 'inbox',
description:
'Review skills extracted from past sessions and move them to global or project skills',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: (
context,
): OpenCustomDialogActionReturn | SlashCommandActionReturn | void => {
const config = context.services.agentContext?.config;
if (!config) {
return {
type: 'message',
messageType: 'error',
content: 'Config not loaded.',
};
}
if (!config.isAutoMemoryEnabled()) {
return {
type: 'message',
messageType: 'info',
content:
'The memory inbox requires Auto Memory. Enable it with: experimental.autoMemory = true in settings.',
};
}
return {
type: 'custom_dialog',
component: React.createElement(InboxDialog, {
config,
onClose: () => context.ui.removeComponent(),
onReloadSkills: async () => {
await config.reloadSkills();
context.ui.reloadCommands();
},
onReloadMemory: async () => {
await refreshMemory(config);
},
}),
};
},
},
],
return result;
},
};
const reloadSubCommand: SlashCommand = {
name: 'reload',
altNames: ['refresh'],
description: 'Reload the memory from the source',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context) => {
context.ui.addItem(
{
type: MessageType.INFO,
text: 'Reloading memory from source files...',
},
Date.now(),
);
try {
const config = context.services.agentContext?.config;
if (config) {
const result = await refreshMemory(config);
context.ui.addItem(
{
type: MessageType.INFO,
text: result.content,
},
Date.now(),
);
}
} catch (error) {
context.ui.addItem(
{
type: MessageType.ERROR,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
text: `Error reloading memory: ${(error as Error).message}`,
},
Date.now(),
);
}
},
};
const listSubCommand: SlashCommand = {
name: 'list',
description: 'Lists the paths of the GEMINI.md files in use',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context) => {
const config = context.services.agentContext?.config;
if (!config) return;
const result = listMemoryFiles(config);
context.ui.addItem(
{
type: MessageType.INFO,
text: result.content,
},
Date.now(),
);
},
};
const inboxSubCommand: SlashCommand = {
name: 'inbox',
description:
'Review skills extracted from past sessions and move them to global or project skills',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: (
context,
): OpenCustomDialogActionReturn | SlashCommandActionReturn | void => {
const config = context.services.agentContext?.config;
if (!config) {
return {
type: 'message',
messageType: 'error',
content: 'Config not loaded.',
};
}
if (!config.isAutoMemoryEnabled()) {
return {
type: 'message',
messageType: 'info',
content:
'The memory inbox requires Auto Memory. Enable it with: experimental.autoMemory = true in settings.',
};
}
return {
type: 'custom_dialog',
component: React.createElement(InboxDialog, {
config,
onClose: () => context.ui.removeComponent(),
onReloadSkills: async () => {
await config.reloadSkills();
context.ui.reloadCommands();
},
onReloadMemory: async () => {
await refreshMemory(config);
},
}),
};
},
};
export const memoryCommand = (config: Config | null): SlashCommand => {
// The `add` subcommand depends on the `save_memory` tool, which is not
// registered when Memory v2 is enabled. Omit it in that case.
const isMemoryV2 = config?.isMemoryV2Enabled() ?? false;
const subCommands: SlashCommand[] = [
showSubCommand,
...(isMemoryV2 ? [] : [addSubCommand]),
reloadSubCommand,
listSubCommand,
inboxSubCommand,
];
return {
name: 'memory',
description: 'Commands for interacting with memory',
kind: CommandKind.BUILT_IN,
autoExecute: false,
subCommands,
};
};