diff options
Diffstat (limited to 'packages/cli/src/ui')
| -rw-r--r-- | packages/cli/src/ui/commands/chatCommand.test.ts | 70 | ||||
| -rw-r--r-- | packages/cli/src/ui/commands/chatCommand.ts | 42 |
2 files changed, 108 insertions, 4 deletions
diff --git a/packages/cli/src/ui/commands/chatCommand.test.ts b/packages/cli/src/ui/commands/chatCommand.test.ts index 0c98239a..aad0897c 100644 --- a/packages/cli/src/ui/commands/chatCommand.test.ts +++ b/packages/cli/src/ui/commands/chatCommand.test.ts @@ -40,14 +40,17 @@ describe('chatCommand', () => { let mockGetChat: ReturnType<typeof vi.fn>; let mockSaveCheckpoint: ReturnType<typeof vi.fn>; let mockLoadCheckpoint: ReturnType<typeof vi.fn>; + let mockDeleteCheckpoint: ReturnType<typeof vi.fn>; let mockGetHistory: ReturnType<typeof vi.fn>; - const getSubCommand = (name: 'list' | 'save' | 'resume'): SlashCommand => { + const getSubCommand = ( + name: 'list' | 'save' | 'resume' | 'delete', + ): SlashCommand => { const subCommand = chatCommand.subCommands?.find( (cmd) => cmd.name === name, ); if (!subCommand) { - throw new Error(`/memory ${name} command not found.`); + throw new Error(`/chat ${name} command not found.`); } return subCommand; }; @@ -59,6 +62,7 @@ describe('chatCommand', () => { }); mockSaveCheckpoint = vi.fn().mockResolvedValue(undefined); mockLoadCheckpoint = vi.fn().mockResolvedValue([]); + mockDeleteCheckpoint = vi.fn().mockResolvedValue(true); mockContext = createMockCommandContext({ services: { @@ -72,6 +76,7 @@ describe('chatCommand', () => { logger: { saveCheckpoint: mockSaveCheckpoint, loadCheckpoint: mockLoadCheckpoint, + deleteCheckpoint: mockDeleteCheckpoint, initialize: vi.fn().mockResolvedValue(undefined), }, }, @@ -85,7 +90,7 @@ describe('chatCommand', () => { it('should have the correct main command definition', () => { expect(chatCommand.name).toBe('chat'); expect(chatCommand.description).toBe('Manage conversation history.'); - expect(chatCommand.subCommands).toHaveLength(3); + expect(chatCommand.subCommands).toHaveLength(4); }); describe('list subcommand', () => { @@ -297,4 +302,63 @@ describe('chatCommand', () => { }); }); }); + + describe('delete subcommand', () => { + let deleteCommand: SlashCommand; + const tag = 'my-tag'; + beforeEach(() => { + deleteCommand = getSubCommand('delete'); + }); + + it('should return an error if tag is missing', async () => { + const result = await deleteCommand?.action?.(mockContext, ' '); + expect(result).toEqual({ + type: 'message', + messageType: 'error', + content: 'Missing tag. Usage: /chat delete <tag>', + }); + }); + + it('should return an error if checkpoint is not found', async () => { + mockDeleteCheckpoint.mockResolvedValue(false); + const result = await deleteCommand?.action?.(mockContext, tag); + expect(result).toEqual({ + type: 'message', + messageType: 'error', + content: `Error: No checkpoint found with tag '${tag}'.`, + }); + }); + + it('should delete the conversation', async () => { + const result = await deleteCommand?.action?.(mockContext, tag); + + expect(mockDeleteCheckpoint).toHaveBeenCalledWith(tag); + expect(result).toEqual({ + type: 'message', + messageType: 'info', + content: `Conversation checkpoint '${tag}' has been deleted.`, + }); + }); + + describe('completion', () => { + it('should provide completion suggestions', async () => { + const fakeFiles = ['checkpoint-alpha.json', 'checkpoint-beta.json']; + mockFs.readdir.mockImplementation( + (async (_: string): Promise<string[]> => + fakeFiles as string[]) as unknown as typeof fsPromises.readdir, + ); + + mockFs.stat.mockImplementation( + (async (_: string): Promise<Stats> => + ({ + mtime: new Date(), + }) as Stats) as unknown as typeof fsPromises.stat, + ); + + const result = await deleteCommand?.completion?.(mockContext, 'a'); + + expect(result).toEqual(['alpha']); + }); + }); + }); }); diff --git a/packages/cli/src/ui/commands/chatCommand.ts b/packages/cli/src/ui/commands/chatCommand.ts index 739097e3..a5fa13da 100644 --- a/packages/cli/src/ui/commands/chatCommand.ts +++ b/packages/cli/src/ui/commands/chatCommand.ts @@ -206,9 +206,49 @@ const resumeCommand: SlashCommand = { }, }; +const deleteCommand: SlashCommand = { + name: 'delete', + description: 'Delete a conversation checkpoint. Usage: /chat delete <tag>', + kind: CommandKind.BUILT_IN, + action: async (context, args): Promise<MessageActionReturn> => { + const tag = args.trim(); + if (!tag) { + return { + type: 'message', + messageType: 'error', + content: 'Missing tag. Usage: /chat delete <tag>', + }; + } + + const { logger } = context.services; + await logger.initialize(); + const deleted = await logger.deleteCheckpoint(tag); + + if (deleted) { + return { + type: 'message', + messageType: 'info', + content: `Conversation checkpoint '${tag}' has been deleted.`, + }; + } else { + return { + type: 'message', + messageType: 'error', + content: `Error: No checkpoint found with tag '${tag}'.`, + }; + } + }, + completion: async (context, partialArg) => { + const chatDetails = await getSavedChatTags(context, true); + return chatDetails + .map((chat) => chat.name) + .filter((name) => name.startsWith(partialArg)); + }, +}; + export const chatCommand: SlashCommand = { name: 'chat', description: 'Manage conversation history.', kind: CommandKind.BUILT_IN, - subCommands: [listCommand, saveCommand, resumeCommand], + subCommands: [listCommand, saveCommand, resumeCommand, deleteCommand], }; |
