From 91fcca3b1c77590c500d27350dff9c2dbb1d0c21 Mon Sep 17 00:00:00 2001 From: Sehoon Shon Date: Tue, 13 Jan 2026 14:15:04 -0500 Subject: [PATCH] refactor: make baseTimestamp optional in addItem and remove redundant calls (#16471) --- .../cli/src/ui/commands/aboutCommand.test.ts | 28 +- packages/cli/src/ui/commands/aboutCommand.ts | 2 +- .../cli/src/ui/commands/agentsCommand.test.ts | 1 - packages/cli/src/ui/commands/agentsCommand.ts | 13 +- .../cli/src/ui/commands/chatCommand.test.ts | 29 +- packages/cli/src/ui/commands/chatCommand.ts | 2 +- .../src/ui/commands/directoryCommand.test.tsx | 9 - .../cli/src/ui/commands/directoryCommand.tsx | 99 ++--- .../src/ui/commands/extensionsCommand.test.ts | 326 ++++++--------- .../cli/src/ui/commands/extensionsCommand.ts | 374 +++++++----------- .../cli/src/ui/commands/helpCommand.test.ts | 1 - packages/cli/src/ui/commands/helpCommand.ts | 2 +- .../cli/src/ui/commands/hooksCommand.test.ts | 4 - packages/cli/src/ui/commands/hooksCommand.ts | 2 +- .../cli/src/ui/commands/mcpCommand.test.ts | 3 - packages/cli/src/ui/commands/mcpCommand.ts | 48 +-- .../cli/src/ui/commands/skillsCommand.test.ts | 13 - packages/cli/src/ui/commands/skillsCommand.ts | 105 ++--- .../cli/src/ui/commands/statsCommand.test.ts | 30 +- packages/cli/src/ui/commands/statsCommand.ts | 31 +- .../cli/src/ui/commands/toolsCommand.test.ts | 24 +- packages/cli/src/ui/commands/toolsCommand.ts | 13 +- .../MultiFolderTrustDialog.test.tsx | 13 +- .../ui/components/MultiFolderTrustDialog.tsx | 18 +- .../cli/src/ui/hooks/useGeminiStream.test.tsx | 69 ++-- packages/cli/src/ui/hooks/useGeminiStream.ts | 113 ++---- .../src/ui/hooks/useHistoryManager.test.ts | 19 + .../cli/src/ui/hooks/useHistoryManager.ts | 4 +- .../src/ui/hooks/useIncludeDirsTrust.test.tsx | 3 +- .../cli/src/ui/hooks/useIncludeDirsTrust.tsx | 18 +- 30 files changed, 528 insertions(+), 888 deletions(-) diff --git a/packages/cli/src/ui/commands/aboutCommand.test.ts b/packages/cli/src/ui/commands/aboutCommand.test.ts index b21dfa5233..9b93641958 100644 --- a/packages/cli/src/ui/commands/aboutCommand.test.ts +++ b/packages/cli/src/ui/commands/aboutCommand.test.ts @@ -87,20 +87,17 @@ describe('aboutCommand', () => { await aboutCommand.action(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ABOUT, - cliVersion: 'test-version', - osVersion: 'test-os', - sandboxEnv: 'no sandbox', - modelVersion: 'test-model', - selectedAuthType: 'test-auth', - gcpProject: 'test-gcp-project', - ideClient: 'test-ide', - userEmail: 'test-email@example.com', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ABOUT, + cliVersion: 'test-version', + osVersion: 'test-os', + sandboxEnv: 'no sandbox', + modelVersion: 'test-model', + selectedAuthType: 'test-auth', + gcpProject: 'test-gcp-project', + ideClient: 'test-ide', + userEmail: 'test-email@example.com', + }); }); it('should show the correct sandbox environment variable', async () => { @@ -115,7 +112,6 @@ describe('aboutCommand', () => { expect.objectContaining({ sandboxEnv: 'gemini-sandbox', }), - expect.any(Number), ); }); @@ -132,7 +128,6 @@ describe('aboutCommand', () => { expect.objectContaining({ sandboxEnv: 'sandbox-exec (test-profile)', }), - expect.any(Number), ); }); @@ -159,7 +154,6 @@ describe('aboutCommand', () => { gcpProject: 'test-gcp-project', ideClient: '', }), - expect.any(Number), ); }); }); diff --git a/packages/cli/src/ui/commands/aboutCommand.ts b/packages/cli/src/ui/commands/aboutCommand.ts index 9b4cc34d0c..46589a0c99 100644 --- a/packages/cli/src/ui/commands/aboutCommand.ts +++ b/packages/cli/src/ui/commands/aboutCommand.ts @@ -56,7 +56,7 @@ export const aboutCommand: SlashCommand = { userEmail, }; - context.ui.addItem(aboutItem, Date.now()); + context.ui.addItem(aboutItem); }, }; diff --git a/packages/cli/src/ui/commands/agentsCommand.test.ts b/packages/cli/src/ui/commands/agentsCommand.test.ts index 5c1fe5892d..bc84252cf2 100644 --- a/packages/cli/src/ui/commands/agentsCommand.test.ts +++ b/packages/cli/src/ui/commands/agentsCommand.test.ts @@ -79,7 +79,6 @@ describe('agentsCommand', () => { type: MessageType.AGENTS_LIST, agents: mockAgents, }), - expect.any(Number), ); }); diff --git a/packages/cli/src/ui/commands/agentsCommand.ts b/packages/cli/src/ui/commands/agentsCommand.ts index 690396b798..5059fc1937 100644 --- a/packages/cli/src/ui/commands/agentsCommand.ts +++ b/packages/cli/src/ui/commands/agentsCommand.ts @@ -44,7 +44,7 @@ const agentsListCommand: SlashCommand = { agents, }; - context.ui.addItem(agentsListItem, Date.now()); + context.ui.addItem(agentsListItem); return; }, @@ -65,13 +65,10 @@ const agentsRefreshCommand: SlashCommand = { }; } - context.ui.addItem( - { - type: MessageType.INFO, - text: 'Refreshing agent registry...', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: 'Refreshing agent registry...', + }); await agentRegistry.reload(); diff --git a/packages/cli/src/ui/commands/chatCommand.test.ts b/packages/cli/src/ui/commands/chatCommand.test.ts index 6edae787d2..6ff8d8a52e 100644 --- a/packages/cli/src/ui/commands/chatCommand.test.ts +++ b/packages/cli/src/ui/commands/chatCommand.test.ts @@ -127,22 +127,19 @@ describe('chatCommand', () => { await listCommand?.action?.(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: 'chat_list', - chats: [ - { - name: 'test1', - mtime: date1.toISOString(), - }, - { - name: 'test2', - mtime: date2.toISOString(), - }, - ], - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: 'chat_list', + chats: [ + { + name: 'test1', + mtime: date1.toISOString(), + }, + { + name: 'test2', + mtime: date2.toISOString(), + }, + ], + }); }); }); describe('save subcommand', () => { diff --git a/packages/cli/src/ui/commands/chatCommand.ts b/packages/cli/src/ui/commands/chatCommand.ts index 89a770e1f8..3dafe59554 100644 --- a/packages/cli/src/ui/commands/chatCommand.ts +++ b/packages/cli/src/ui/commands/chatCommand.ts @@ -81,7 +81,7 @@ const listCommand: SlashCommand = { chats: chatDetails, }; - context.ui.addItem(item, Date.now()); + context.ui.addItem(item); }, }; diff --git a/packages/cli/src/ui/commands/directoryCommand.test.tsx b/packages/cli/src/ui/commands/directoryCommand.test.tsx index 1c43149440..45ddd6fbe2 100644 --- a/packages/cli/src/ui/commands/directoryCommand.test.tsx +++ b/packages/cli/src/ui/commands/directoryCommand.test.tsx @@ -94,7 +94,6 @@ describe('directoryCommand', () => { '/home/user/project1', )}\n- ${path.normalize('/home/user/project2')}`, }), - expect.any(Number), ); }); }); @@ -121,7 +120,6 @@ describe('directoryCommand', () => { type: MessageType.ERROR, text: 'Please provide at least one path to add.', }), - expect.any(Number), ); }); @@ -135,7 +133,6 @@ describe('directoryCommand', () => { type: MessageType.INFO, text: `Successfully added directories:\n- ${newPath}`, }), - expect.any(Number), ); }); @@ -151,7 +148,6 @@ describe('directoryCommand', () => { type: MessageType.INFO, text: `Successfully added directories:\n- ${newPath1}\n- ${newPath2}`, }), - expect.any(Number), ); }); @@ -168,7 +164,6 @@ describe('directoryCommand', () => { type: MessageType.ERROR, text: `Error adding '${newPath}': ${error.message}`, }), - expect.any(Number), ); }); @@ -191,7 +186,6 @@ describe('directoryCommand', () => { type: MessageType.INFO, text: `The following directories are already in the workspace:\n- ${existingPath}`, }), - expect.any(Number), ); expect(mockWorkspaceContext.addDirectory).not.toHaveBeenCalledWith( existingPath, @@ -218,7 +212,6 @@ describe('directoryCommand', () => { type: MessageType.INFO, text: `Successfully added directories:\n- ${validPath}`, }), - expect.any(Number), ); expect(mockContext.ui.addItem).toHaveBeenCalledWith( @@ -226,7 +219,6 @@ describe('directoryCommand', () => { type: MessageType.ERROR, text: `Error adding '${invalidPath}': ${error.message}`, }), - expect.any(Number), ); }); @@ -317,7 +309,6 @@ describe('directoryCommand', () => { type: MessageType.ERROR, text: expect.stringContaining('explicitly untrusted'), }), - expect.any(Number), ); }); diff --git a/packages/cli/src/ui/commands/directoryCommand.tsx b/packages/cli/src/ui/commands/directoryCommand.tsx index 872945ecea..c3c56d46f2 100644 --- a/packages/cli/src/ui/commands/directoryCommand.tsx +++ b/packages/cli/src/ui/commands/directoryCommand.tsx @@ -22,18 +22,18 @@ import type { Config } from '@google/gemini-cli-core'; async function finishAddingDirectories( config: Config, - addItem: (itemData: Omit, baseTimestamp: number) => number, + addItem: ( + itemData: Omit, + baseTimestamp?: number, + ) => number, added: string[], errors: string[], ) { if (!config) { - addItem( - { - type: MessageType.ERROR, - text: 'Configuration is not available.', - }, - Date.now(), - ); + addItem({ + type: MessageType.ERROR, + text: 'Configuration is not available.', + }); return; } @@ -41,13 +41,10 @@ async function finishAddingDirectories( if (config.shouldLoadMemoryFromIncludeDirectories()) { await refreshServerHierarchicalMemory(config); } - addItem( - { - type: MessageType.INFO, - text: `Successfully added GEMINI.md files from the following directories if there are:\n- ${added.join('\n- ')}`, - }, - Date.now(), - ); + addItem({ + type: MessageType.INFO, + text: `Successfully added GEMINI.md files from the following directories if there are:\n- ${added.join('\n- ')}`, + }); } catch (error) { errors.push(`Error refreshing memory: ${(error as Error).message}`); } @@ -57,17 +54,14 @@ async function finishAddingDirectories( if (gemini) { await gemini.addDirectoryContext(); } - addItem( - { - type: MessageType.INFO, - text: `Successfully added directories:\n- ${added.join('\n- ')}`, - }, - Date.now(), - ); + addItem({ + type: MessageType.INFO, + text: `Successfully added directories:\n- ${added.join('\n- ')}`, + }); } if (errors.length > 0) { - addItem({ type: MessageType.ERROR, text: errors.join('\n') }, Date.now()); + addItem({ type: MessageType.ERROR, text: errors.join('\n') }); } } @@ -112,13 +106,10 @@ export const directoryCommand: SlashCommand = { const [...rest] = args.split(' '); if (!config) { - addItem( - { - type: MessageType.ERROR, - text: 'Configuration is not available.', - }, - Date.now(), - ); + addItem({ + type: MessageType.ERROR, + text: 'Configuration is not available.', + }); return; } @@ -136,13 +127,10 @@ export const directoryCommand: SlashCommand = { .split(',') .filter((p) => p); if (pathsToAdd.length === 0) { - addItem( - { - type: MessageType.ERROR, - text: 'Please provide at least one path to add.', - }, - Date.now(), - ); + addItem({ + type: MessageType.ERROR, + text: 'Please provide at least one path to add.', + }); return; } @@ -164,15 +152,12 @@ export const directoryCommand: SlashCommand = { } if (alreadyAdded.length > 0) { - addItem( - { - type: MessageType.INFO, - text: `The following directories are already in the workspace:\n- ${alreadyAdded.join( - '\n- ', - )}`, - }, - Date.now(), - ); + addItem({ + type: MessageType.INFO, + text: `The following directories are already in the workspace:\n- ${alreadyAdded.join( + '\n- ', + )}`, + }); } if (pathsToProcess.length === 0) { @@ -262,25 +247,19 @@ export const directoryCommand: SlashCommand = { services: { config }, } = context; if (!config) { - addItem( - { - type: MessageType.ERROR, - text: 'Configuration is not available.', - }, - Date.now(), - ); + addItem({ + type: MessageType.ERROR, + text: 'Configuration is not available.', + }); return; } const workspaceContext = config.getWorkspaceContext(); const directories = workspaceContext.getDirectories(); const directoryList = directories.map((dir) => `- ${dir}`).join('\n'); - addItem( - { - type: MessageType.INFO, - text: `Current workspace directories:\n${directoryList}`, - }, - Date.now(), - ); + addItem({ + type: MessageType.INFO, + text: `Current workspace directories:\n${directoryList}`, + }); }, }, ], diff --git a/packages/cli/src/ui/commands/extensionsCommand.test.ts b/packages/cli/src/ui/commands/extensionsCommand.test.ts index 55f20eb25d..9e46ab47aa 100644 --- a/packages/cli/src/ui/commands/extensionsCommand.test.ts +++ b/packages/cli/src/ui/commands/extensionsCommand.test.ts @@ -148,13 +148,10 @@ describe('extensionsCommand', () => { if (!command.action) throw new Error('Action not defined'); await command.action(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.EXTENSIONS_LIST, - extensions: expect.any(Array), - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.EXTENSIONS_LIST, + extensions: expect.any(Array), + }); }); it('should show a message if no extensions are installed', async () => { @@ -163,13 +160,10 @@ describe('extensionsCommand', () => { if (!command.action) throw new Error('Action not defined'); await command.action(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: 'No extensions installed. Run `/extensions explore` to check out the gallery.', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: 'No extensions installed. Run `/extensions explore` to check out the gallery.', + }); }); }); @@ -244,26 +238,20 @@ describe('extensionsCommand', () => { it('should show usage if no args are provided', async () => { await updateAction(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Usage: /extensions update |--all', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Usage: /extensions update |--all', + }); }); it('should show a message if no extensions are installed', async () => { mockGetExtensions.mockReturnValue([]); await updateAction(mockContext, 'ext-one'); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: 'No extensions installed. Run `/extensions explore` to check out the gallery.', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: 'No extensions installed. Run `/extensions explore` to check out the gallery.', + }); }); it('should inform user if there are no extensions to update with --all', async () => { @@ -276,13 +264,10 @@ describe('extensionsCommand', () => { ); await updateAction(mockContext, '--all'); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: 'No extensions to update.', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: 'No extensions to update.', + }); }); it('should call setPendingItem and addItem in a finally block on success', async () => { @@ -310,13 +295,10 @@ describe('extensionsCommand', () => { extensions: expect.any(Array), }); expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith(null); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.EXTENSIONS_LIST, - extensions: expect.any(Array), - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.EXTENSIONS_LIST, + extensions: expect.any(Array), + }); }); it('should call setPendingItem and addItem in a finally block on failure', async () => { @@ -329,20 +311,14 @@ describe('extensionsCommand', () => { extensions: expect.any(Array), }); expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith(null); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.EXTENSIONS_LIST, - extensions: expect.any(Array), - }, - expect.any(Number), - ); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Something went wrong', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.EXTENSIONS_LIST, + extensions: expect.any(Array), + }); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Something went wrong', + }); }); it('should update a single extension by name', async () => { @@ -403,13 +379,10 @@ describe('extensionsCommand', () => { extensions: expect.any(Array), }); expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith(null); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.EXTENSIONS_LIST, - extensions: expect.any(Array), - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.EXTENSIONS_LIST, + extensions: expect.any(Array), + }); }); }); @@ -430,13 +403,10 @@ describe('extensionsCommand', () => { await exploreAction(mockContext, ''); const extensionsUrl = 'https://geminicli.com/extensions/'; - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `Opening extensions page in your browser: ${extensionsUrl}`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `Opening extensions page in your browser: ${extensionsUrl}`, + }); expect(open).toHaveBeenCalledWith(extensionsUrl); }); @@ -449,13 +419,10 @@ describe('extensionsCommand', () => { await exploreAction(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `View available extensions at ${extensionsUrl}`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `View available extensions at ${extensionsUrl}`, + }); // Ensure 'open' was not called in the sandbox expect(open).not.toHaveBeenCalled(); @@ -468,13 +435,10 @@ describe('extensionsCommand', () => { await exploreAction(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `Would open extensions page in your browser: ${extensionsUrl} (skipped in test environment)`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `Would open extensions page in your browser: ${extensionsUrl} (skipped in test environment)`, + }); // Ensure 'open' was not called in test environment expect(open).not.toHaveBeenCalled(); @@ -488,13 +452,10 @@ describe('extensionsCommand', () => { await exploreAction(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: `Failed to open browser. Check out the extensions gallery at ${extensionsUrl}`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: `Failed to open browser. Check out the extensions gallery at ${extensionsUrl}`, + }); }); }); @@ -549,13 +510,10 @@ describe('extensionsCommand', () => { it('should show usage if no extension name is provided', async () => { await installAction!(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Usage: /extensions install ', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Usage: /extensions install ', + }); expect(mockInstallExtension).not.toHaveBeenCalled(); }); @@ -572,20 +530,14 @@ describe('extensionsCommand', () => { source: packageName, type: 'git', }); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `Installing extension from "${packageName}"...`, - }, - expect.any(Number), - ); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `Extension "${packageName}" installed successfully.`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `Installing extension from "${packageName}"...`, + }); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `Extension "${packageName}" installed successfully.`, + }); }); it('should show error message on installation failure', async () => { @@ -603,25 +555,19 @@ describe('extensionsCommand', () => { source: packageName, type: 'git', }); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: `Failed to install extension from "${packageName}": ${errorMessage}`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: `Failed to install extension from "${packageName}": ${errorMessage}`, + }); }); it('should show error message for invalid source', async () => { const invalidSource = 'a;b'; await installAction!(mockContext, invalidSource); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: `Invalid source: ${invalidSource}`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: `Invalid source: ${invalidSource}`, + }); expect(mockInstallExtension).not.toHaveBeenCalled(); }); }); @@ -640,13 +586,10 @@ describe('extensionsCommand', () => { it('should show usage if no extension is provided', async () => { await linkAction!(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Usage: /extensions link ', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Usage: /extensions link ', + }); expect(mockInstallExtension).not.toHaveBeenCalled(); }); @@ -661,20 +604,14 @@ describe('extensionsCommand', () => { source: packageName, type: 'link', }); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `Linking extension from "${packageName}"...`, - }, - expect.any(Number), - ); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `Extension "${packageName}" linked successfully.`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `Linking extension from "${packageName}"...`, + }); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `Extension "${packageName}" linked successfully.`, + }); }); it('should show error message on linking failure', async () => { @@ -690,13 +627,10 @@ describe('extensionsCommand', () => { source: packageName, type: 'link', }); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: `Failed to link extension from "${packageName}": ${errorMessage}`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: `Failed to link extension from "${packageName}": ${errorMessage}`, + }); }); it('should show error message for invalid source', async () => { @@ -723,13 +657,10 @@ describe('extensionsCommand', () => { it('should show usage if no extension name is provided', async () => { await uninstallAction!(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Usage: /extensions uninstall ', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Usage: /extensions uninstall ', + }); expect(mockUninstallExtension).not.toHaveBeenCalled(); }); @@ -737,20 +668,14 @@ describe('extensionsCommand', () => { const extensionName = 'test-extension'; await uninstallAction!(mockContext, extensionName); expect(mockUninstallExtension).toHaveBeenCalledWith(extensionName, false); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `Uninstalling extension "${extensionName}"...`, - }, - expect.any(Number), - ); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: `Extension "${extensionName}" uninstalled successfully.`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `Uninstalling extension "${extensionName}"...`, + }); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: `Extension "${extensionName}" uninstalled successfully.`, + }); }); it('should show error message on uninstallation failure', async () => { @@ -760,13 +685,10 @@ describe('extensionsCommand', () => { await uninstallAction!(mockContext, extensionName); expect(mockUninstallExtension).toHaveBeenCalledWith(extensionName, false); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: `Failed to uninstall extension "${extensionName}": ${errorMessage}`, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: `Failed to uninstall extension "${extensionName}": ${errorMessage}`, + }); }); }); @@ -785,13 +707,10 @@ describe('extensionsCommand', () => { it('should show usage if no extension name is provided', async () => { await enableAction!(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Usage: /extensions enable [--scope=]', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Usage: /extensions enable [--scope=]', + }); }); it('should call enableExtension with the provided scope', async () => { @@ -840,13 +759,10 @@ describe('extensionsCommand', () => { it('should show usage if no extension name is provided', async () => { await disableAction!(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Usage: /extensions disable [--scope=]', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Usage: /extensions disable [--scope=]', + }); }); it('should call disableExtension with the provided scope', async () => { @@ -912,13 +828,10 @@ describe('extensionsCommand', () => { await restartAction!(mockContext, '--all'); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: 'No extensions installed. Run `/extensions explore` to check out the gallery.', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: 'No extensions installed. Run `/extensions explore` to check out the gallery.', + }); }); it('restarts all active extensions when --all is provided', async () => { @@ -939,14 +852,12 @@ describe('extensionsCommand', () => { type: MessageType.INFO, text: 'Restarting 2 extensions...', }), - expect.any(Number), ); expect(mockContext.ui.addItem).toHaveBeenCalledWith( expect.objectContaining({ type: MessageType.INFO, text: '2 extensions restarted successfully.', }), - expect.any(Number), ); expect(mockContext.ui.dispatchExtensionStateUpdate).toHaveBeenCalledWith({ type: 'RESTARTED', @@ -986,7 +897,6 @@ describe('extensionsCommand', () => { type: MessageType.ERROR, text: "Extensions are not yet loaded, can't restart yet", }), - expect.any(Number), ); expect(mockRestartExtension).not.toHaveBeenCalled(); }); @@ -999,7 +909,6 @@ describe('extensionsCommand', () => { type: MessageType.ERROR, text: 'Usage: /extensions restart |--all', }), - expect.any(Number), ); expect(mockRestartExtension).not.toHaveBeenCalled(); }); @@ -1019,7 +928,6 @@ describe('extensionsCommand', () => { type: MessageType.ERROR, text: 'Failed to restart some extensions:\n ext1: Failed to restart', }), - expect.any(Number), ); }); @@ -1038,7 +946,6 @@ describe('extensionsCommand', () => { type: MessageType.WARNING, text: 'Extension(s) not found or not active: ext2', }), - expect.any(Number), ); }); @@ -1056,7 +963,6 @@ describe('extensionsCommand', () => { type: MessageType.WARNING, text: 'Extension(s) not found or not active: ext2, ext3', }), - expect.any(Number), ); }); diff --git a/packages/cli/src/ui/commands/extensionsCommand.ts b/packages/cli/src/ui/commands/extensionsCommand.ts index 7c21115880..6aa748153a 100644 --- a/packages/cli/src/ui/commands/extensionsCommand.ts +++ b/packages/cli/src/ui/commands/extensionsCommand.ts @@ -37,13 +37,10 @@ function showMessageIfNoExtensions( extensions: unknown[], ): boolean { if (extensions.length === 0) { - context.ui.addItem( - { - type: MessageType.INFO, - text: 'No extensions installed. Run `/extensions explore` to check out the gallery.', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: 'No extensions installed. Run `/extensions explore` to check out the gallery.', + }); return true; } return false; @@ -63,7 +60,7 @@ async function listAction(context: CommandContext) { extensions, }; - context.ui.addItem(historyItem, Date.now()); + context.ui.addItem(historyItem); } function updateAction(context: CommandContext, args: string): Promise { @@ -72,13 +69,10 @@ function updateAction(context: CommandContext, args: string): Promise { const names = all ? null : updateArgs; if (!all && names?.length === 0) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: 'Usage: /extensions update |--all', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: 'Usage: /extensions update |--all', + }); return Promise.resolve(); } @@ -103,16 +97,13 @@ function updateAction(context: CommandContext, args: string): Promise { // eslint-disable-next-line @typescript-eslint/no-floating-promises updateComplete.then((updateInfos) => { if (updateInfos.length === 0) { - context.ui.addItem( - { - type: MessageType.INFO, - text: 'No extensions to update.', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: 'No extensions to update.', + }); } - context.ui.addItem(historyItem, Date.now()); + context.ui.addItem(historyItem); context.ui.setPendingItem(null); }); @@ -136,26 +127,20 @@ function updateAction(context: CommandContext, args: string): Promise { (extension) => extension.name === name, ); if (!extension) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Extension ${name} not found.`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Extension ${name} not found.`, + }); continue; } } } } catch (error) { resolveUpdateComplete!([]); - context.ui.addItem( - { - type: MessageType.ERROR, - text: getErrorMessage(error), - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: getErrorMessage(error), + }); } return updateComplete.then((_) => {}); } @@ -166,13 +151,10 @@ async function restartAction( ): Promise { const extensionLoader = context.services.config?.getExtensionLoader(); if (!extensionLoader) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: "Extensions are not yet loaded, can't restart yet", - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: "Extensions are not yet loaded, can't restart yet", + }); return; } @@ -185,13 +167,10 @@ async function restartAction( const all = restartArgs.length === 1 && restartArgs[0] === '--all'; const names = all ? null : restartArgs; if (!all && names?.length === 0) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: 'Usage: /extensions restart |--all', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: 'Usage: /extensions restart |--all', + }); return Promise.resolve(); } @@ -208,15 +187,10 @@ async function restartAction( !extensionsToRestart.some((extension) => extension.name === name), ); if (notFound.length > 0) { - context.ui.addItem( - { - type: MessageType.WARNING, - text: `Extension(s) not found or not active: ${notFound.join( - ', ', - )}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.WARNING, + text: `Extension(s) not found or not active: ${notFound.join(', ')}`, + }); } } } @@ -232,7 +206,7 @@ async function restartAction( text: `Restarting ${extensionsToRestart.length} extension${s}...`, color: theme.text.primary, }; - context.ui.addItem(restartingMessage, Date.now()); + context.ui.addItem(restartingMessage); const results = await Promise.allSettled( extensionsToRestart.map(async (extension) => { @@ -259,13 +233,10 @@ async function restartAction( return `${extensionName}: ${getErrorMessage(failure.reason)}`; }) .join('\n '); - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Failed to restart some extensions:\n ${errorMessages}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Failed to restart some extensions:\n ${errorMessages}`, + }); } else { const infoItem: HistoryItemInfo = { type: MessageType.INFO, @@ -273,7 +244,7 @@ async function restartAction( icon: emptyIcon, color: theme.text.primary, }; - context.ui.addItem(infoItem, Date.now()); + context.ui.addItem(infoItem); } } @@ -282,42 +253,30 @@ async function exploreAction(context: CommandContext) { // Only check for NODE_ENV for explicit test mode, not for unit test framework if (process.env['NODE_ENV'] === 'test') { - context.ui.addItem( - { - type: MessageType.INFO, - text: `Would open extensions page in your browser: ${extensionsUrl} (skipped in test environment)`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Would open extensions page in your browser: ${extensionsUrl} (skipped in test environment)`, + }); } else if ( process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec' ) { - context.ui.addItem( - { - type: MessageType.INFO, - text: `View available extensions at ${extensionsUrl}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `View available extensions at ${extensionsUrl}`, + }); } else { - context.ui.addItem( - { - type: MessageType.INFO, - text: `Opening extensions page in your browser: ${extensionsUrl}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Opening extensions page in your browser: ${extensionsUrl}`, + }); try { await open(extensionsUrl); } catch (_error) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Failed to open browser. Check out the extensions gallery at ${extensionsUrl}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Failed to open browser. Check out the extensions gallery at ${extensionsUrl}`, + }); } } } @@ -346,13 +305,10 @@ function getEnableDisableContext( (parts.length === 3 && parts[1] === '--scope') // --scope ) ) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Usage: /extensions ${context.invocation?.name} [--scope=]`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Usage: /extensions ${context.invocation?.name} [--scope=]`, + }); return null; } let scope: SettingScope; @@ -372,13 +328,10 @@ function getEnableDisableContext( scope = SettingScope.Session; break; default: - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Unsupported scope ${parts[2]}, should be one of "user", "workspace", or "session"`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Unsupported scope ${parts[2]}, should be one of "user", "workspace", or "session"`, + }); debugLogger.error(); return null; } @@ -410,13 +363,10 @@ async function disableAction(context: CommandContext, args: string) { const { names, scope, extensionManager } = enableContext; for (const name of names) { await extensionManager.disableExtension(name, scope); - context.ui.addItem( - { - type: MessageType.INFO, - text: `Extension "${name}" disabled for the scope "${scope}"`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Extension "${name}" disabled for the scope "${scope}"`, + }); } } @@ -427,13 +377,10 @@ async function enableAction(context: CommandContext, args: string) { const { names, scope, extensionManager } = enableContext; for (const name of names) { await extensionManager.enableExtension(name, scope); - context.ui.addItem( - { - type: MessageType.INFO, - text: `Extension "${name}" enabled for the scope "${scope}"`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Extension "${name}" enabled for the scope "${scope}"`, + }); } } @@ -448,13 +395,10 @@ async function installAction(context: CommandContext, args: string) { const source = args.trim(); if (!source) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Usage: /extensions install `, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Usage: /extensions install `, + }); return; } @@ -473,45 +417,33 @@ async function installAction(context: CommandContext, args: string) { } if (!isValid) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Invalid source: ${source}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Invalid source: ${source}`, + }); return; } - context.ui.addItem( - { - type: MessageType.INFO, - text: `Installing extension from "${source}"...`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Installing extension from "${source}"...`, + }); try { const installMetadata = await inferInstallMetadata(source); const extension = await extensionLoader.installOrUpdateExtension(installMetadata); - context.ui.addItem( - { - type: MessageType.INFO, - text: `Extension "${extension.name}" installed successfully.`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Extension "${extension.name}" installed successfully.`, + }); } catch (error) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Failed to install extension from "${source}": ${getErrorMessage( - error, - )}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Failed to install extension from "${source}": ${getErrorMessage( + error, + )}`, + }); } } @@ -526,49 +458,37 @@ async function linkAction(context: CommandContext, args: string) { const sourceFilepath = args.trim(); if (!sourceFilepath) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Usage: /extensions link `, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Usage: /extensions link `, + }); return; } if (/[;&|`'"]/.test(sourceFilepath)) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Source file path contains disallowed characters: ${sourceFilepath}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Source file path contains disallowed characters: ${sourceFilepath}`, + }); return; } try { await stat(sourceFilepath); } catch (error) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Invalid source: ${sourceFilepath}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Invalid source: ${sourceFilepath}`, + }); debugLogger.error( `Failed to stat path "${sourceFilepath}": ${getErrorMessage(error)}`, ); return; } - context.ui.addItem( - { - type: MessageType.INFO, - text: `Linking extension from "${sourceFilepath}"...`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Linking extension from "${sourceFilepath}"...`, + }); try { const installMetadata: ExtensionInstallMetadata = { @@ -577,23 +497,17 @@ async function linkAction(context: CommandContext, args: string) { }; const extension = await extensionLoader.installOrUpdateExtension(installMetadata); - context.ui.addItem( - { - type: MessageType.INFO, - text: `Extension "${extension.name}" linked successfully.`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Extension "${extension.name}" linked successfully.`, + }); } catch (error) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Failed to link extension from "${sourceFilepath}": ${getErrorMessage( - error, - )}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Failed to link extension from "${sourceFilepath}": ${getErrorMessage( + error, + )}`, + }); } } @@ -608,43 +522,31 @@ async function uninstallAction(context: CommandContext, args: string) { const name = args.trim(); if (!name) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Usage: /extensions uninstall `, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Usage: /extensions uninstall `, + }); return; } - context.ui.addItem( - { - type: MessageType.INFO, - text: `Uninstalling extension "${name}"...`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Uninstalling extension "${name}"...`, + }); try { await extensionLoader.uninstallExtension(name, false); - context.ui.addItem( - { - type: MessageType.INFO, - text: `Extension "${name}" uninstalled successfully.`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: `Extension "${name}" uninstalled successfully.`, + }); } catch (error) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Failed to uninstall extension "${name}": ${getErrorMessage( - error, - )}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Failed to uninstall extension "${name}": ${getErrorMessage( + error, + )}`, + }); } } diff --git a/packages/cli/src/ui/commands/helpCommand.test.ts b/packages/cli/src/ui/commands/helpCommand.test.ts index 9eff142ba0..58b02251f9 100644 --- a/packages/cli/src/ui/commands/helpCommand.test.ts +++ b/packages/cli/src/ui/commands/helpCommand.test.ts @@ -40,7 +40,6 @@ describe('helpCommand', () => { type: MessageType.HELP, timestamp: expect.any(Date), }), - expect.any(Number), ); }); diff --git a/packages/cli/src/ui/commands/helpCommand.ts b/packages/cli/src/ui/commands/helpCommand.ts index f7d469a7e7..cacebafe01 100644 --- a/packages/cli/src/ui/commands/helpCommand.ts +++ b/packages/cli/src/ui/commands/helpCommand.ts @@ -20,6 +20,6 @@ export const helpCommand: SlashCommand = { timestamp: new Date(), }; - context.ui.addItem(helpItem, Date.now()); + context.ui.addItem(helpItem); }, }; diff --git a/packages/cli/src/ui/commands/hooksCommand.test.ts b/packages/cli/src/ui/commands/hooksCommand.test.ts index aa4eb12971..4f9499c0aa 100644 --- a/packages/cli/src/ui/commands/hooksCommand.test.ts +++ b/packages/cli/src/ui/commands/hooksCommand.test.ts @@ -109,7 +109,6 @@ describe('hooksCommand', () => { expect.objectContaining({ type: MessageType.HOOKS_LIST, }), - expect.any(Number), ); }); }); @@ -155,7 +154,6 @@ describe('hooksCommand', () => { type: MessageType.HOOKS_LIST, hooks: [], }), - expect.any(Number), ); }); @@ -179,7 +177,6 @@ describe('hooksCommand', () => { type: MessageType.HOOKS_LIST, hooks: [], }), - expect.any(Number), ); }); @@ -208,7 +205,6 @@ describe('hooksCommand', () => { type: MessageType.HOOKS_LIST, hooks: mockHooks, }), - expect.any(Number), ); }); }); diff --git a/packages/cli/src/ui/commands/hooksCommand.ts b/packages/cli/src/ui/commands/hooksCommand.ts index 1017474952..050bf3045e 100644 --- a/packages/cli/src/ui/commands/hooksCommand.ts +++ b/packages/cli/src/ui/commands/hooksCommand.ts @@ -37,7 +37,7 @@ async function panelAction( hooks: allHooks, }; - context.ui.addItem(hooksListItem, Date.now()); + context.ui.addItem(hooksListItem); } /** diff --git a/packages/cli/src/ui/commands/mcpCommand.test.ts b/packages/cli/src/ui/commands/mcpCommand.test.ts index 85ee967143..83b5dbb179 100644 --- a/packages/cli/src/ui/commands/mcpCommand.test.ts +++ b/packages/cli/src/ui/commands/mcpCommand.test.ts @@ -231,7 +231,6 @@ describe('mcpCommand', () => { }), ]), }), - expect.any(Number), ); }); @@ -246,7 +245,6 @@ describe('mcpCommand', () => { type: MessageType.MCP_STATUS, showDescriptions: true, }), - expect.any(Number), ); }); @@ -261,7 +259,6 @@ describe('mcpCommand', () => { type: MessageType.MCP_STATUS, showDescriptions: false, }), - expect.any(Number), ); }); }); diff --git a/packages/cli/src/ui/commands/mcpCommand.ts b/packages/cli/src/ui/commands/mcpCommand.ts index 8df7a9c397..b0d95bd603 100644 --- a/packages/cli/src/ui/commands/mcpCommand.ts +++ b/packages/cli/src/ui/commands/mcpCommand.ts @@ -91,19 +91,16 @@ const authCommand: SlashCommand = { // The authentication process will discover OAuth requirements automatically const displayListener = (message: string) => { - context.ui.addItem({ type: 'info', text: message }, Date.now()); + context.ui.addItem({ type: 'info', text: message }); }; appEvents.on(AppEvent.OauthDisplayMessage, displayListener); try { - context.ui.addItem( - { - type: 'info', - text: `Starting OAuth authentication for MCP server '${serverName}'...`, - }, - Date.now(), - ); + context.ui.addItem({ + type: 'info', + text: `Starting OAuth authentication for MCP server '${serverName}'...`, + }); // Import dynamically to avoid circular dependencies const { MCPOAuthProvider } = await import('@google/gemini-cli-core'); @@ -122,24 +119,18 @@ const authCommand: SlashCommand = { appEvents, ); - context.ui.addItem( - { - type: 'info', - text: `✅ Successfully authenticated with MCP server '${serverName}'!`, - }, - Date.now(), - ); + context.ui.addItem({ + type: 'info', + text: `✅ Successfully authenticated with MCP server '${serverName}'!`, + }); // Trigger tool re-discovery to pick up authenticated server const mcpClientManager = config.getMcpClientManager(); if (mcpClientManager) { - context.ui.addItem( - { - type: 'info', - text: `Restarting MCP server '${serverName}'...`, - }, - Date.now(), - ); + context.ui.addItem({ + type: 'info', + text: `Restarting MCP server '${serverName}'...`, + }); await mcpClientManager.restartServer(serverName); } // Update the client with the new tools @@ -279,7 +270,7 @@ const listAction = async ( showSchema, }; - context.ui.addItem(mcpStatusItem, Date.now()); + context.ui.addItem(mcpStatusItem); }; const listCommand: SlashCommand = { @@ -335,13 +326,10 @@ const refreshCommand: SlashCommand = { }; } - context.ui.addItem( - { - type: 'info', - text: 'Restarting MCP servers...', - }, - Date.now(), - ); + context.ui.addItem({ + type: 'info', + text: 'Restarting MCP servers...', + }); await mcpClientManager.restart(); diff --git a/packages/cli/src/ui/commands/skillsCommand.test.ts b/packages/cli/src/ui/commands/skillsCommand.test.ts index 3bcfa6ba06..df11195889 100644 --- a/packages/cli/src/ui/commands/skillsCommand.test.ts +++ b/packages/cli/src/ui/commands/skillsCommand.test.ts @@ -91,7 +91,6 @@ describe('skillsCommand', () => { ], showDescriptions: true, }), - expect.any(Number), ); }); @@ -120,7 +119,6 @@ describe('skillsCommand', () => { ], showDescriptions: true, }), - expect.any(Number), ); }); @@ -132,7 +130,6 @@ describe('skillsCommand', () => { expect.objectContaining({ showDescriptions: false, }), - expect.any(Number), ); }); @@ -229,7 +226,6 @@ describe('skillsCommand', () => { type: MessageType.INFO, text: 'Skill "skill1" disabled by adding it to the disabled list in project (/workspace) settings. Use "/skills reload" for it to take effect.', }), - expect.any(Number), ); }); @@ -258,7 +254,6 @@ describe('skillsCommand', () => { type: MessageType.INFO, text: 'Skill "skill1" enabled by removing it from the disabled list in project (/workspace) and user (/user/settings.json) settings. Use "/skills reload" for it to take effect.', }), - expect.any(Number), ); }); @@ -298,7 +293,6 @@ describe('skillsCommand', () => { type: MessageType.INFO, text: 'Skill "skill1" enabled by removing it from the disabled list in project (/workspace) and user (/user/settings.json) settings. Use "/skills reload" for it to take effect.', }), - expect.any(Number), ); }); @@ -313,7 +307,6 @@ describe('skillsCommand', () => { type: MessageType.ERROR, text: 'Skill "non-existent" not found.', }), - expect.any(Number), ); }); }); @@ -359,7 +352,6 @@ describe('skillsCommand', () => { type: MessageType.INFO, text: 'Agent skills reloaded successfully.', }), - expect.any(Number), ); }); @@ -385,7 +377,6 @@ describe('skillsCommand', () => { type: MessageType.INFO, text: 'Agent skills reloaded successfully. 1 newly available skill.', }), - expect.any(Number), ); }); @@ -409,7 +400,6 @@ describe('skillsCommand', () => { type: MessageType.INFO, text: 'Agent skills reloaded successfully. 1 skill no longer available.', }), - expect.any(Number), ); }); @@ -434,7 +424,6 @@ describe('skillsCommand', () => { type: MessageType.INFO, text: 'Agent skills reloaded successfully. 1 newly available skill and 1 skill no longer available.', }), - expect.any(Number), ); }); @@ -451,7 +440,6 @@ describe('skillsCommand', () => { type: MessageType.ERROR, text: 'Could not retrieve configuration.', }), - expect.any(Number), ); }); @@ -477,7 +465,6 @@ describe('skillsCommand', () => { type: MessageType.ERROR, text: 'Failed to reload skills: Reload failed', }), - expect.any(Number), ); }); }); diff --git a/packages/cli/src/ui/commands/skillsCommand.ts b/packages/cli/src/ui/commands/skillsCommand.ts index ca476a32ea..f632501eee 100644 --- a/packages/cli/src/ui/commands/skillsCommand.ts +++ b/packages/cli/src/ui/commands/skillsCommand.ts @@ -39,13 +39,10 @@ async function listAction( const skillManager = context.services.config?.getSkillManager(); if (!skillManager) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: 'Could not retrieve skill manager.', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: 'Could not retrieve skill manager.', + }); return; } @@ -66,7 +63,7 @@ async function listAction( showDescriptions: useShowDescriptions, }; - context.ui.addItem(skillsListItem, Date.now()); + context.ui.addItem(skillsListItem); } async function disableAction( @@ -75,25 +72,19 @@ async function disableAction( ): Promise { const skillName = args.trim(); if (!skillName) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: 'Please provide a skill name to disable.', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: 'Please provide a skill name to disable.', + }); return; } const skillManager = context.services.config?.getSkillManager(); const skill = skillManager?.getSkill(skillName); if (!skill) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Skill "${skillName}" not found.`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Skill "${skillName}" not found.`, + }); return; } @@ -111,13 +102,10 @@ async function disableAction( feedback += ' Use "/skills reload" for it to take effect.'; } - context.ui.addItem( - { - type: MessageType.INFO, - text: feedback, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: feedback, + }); } async function enableAction( @@ -126,13 +114,10 @@ async function enableAction( ): Promise { const skillName = args.trim(); if (!skillName) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: 'Please provide a skill name to enable.', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: 'Please provide a skill name to enable.', + }); return; } @@ -146,13 +131,10 @@ async function enableAction( feedback += ' Use "/skills reload" for it to take effect.'; } - context.ui.addItem( - { - type: MessageType.INFO, - text: feedback, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.INFO, + text: feedback, + }); } async function reloadAction( @@ -160,13 +142,10 @@ async function reloadAction( ): Promise { const config = context.services.config; if (!config) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: 'Could not retrieve configuration.', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: 'Could not retrieve configuration.', + }); return; } @@ -226,27 +205,21 @@ async function reloadAction( successText += ` ${details.join(' and ')}.`; } - context.ui.addItem( - { - type: 'info', - text: successText, - icon: '✓ ', - color: 'green', - } as HistoryItemInfo, - Date.now(), - ); + context.ui.addItem({ + type: 'info', + text: successText, + icon: '✓ ', + color: 'green', + } as HistoryItemInfo); } catch (error) { clearTimeout(pendingTimeout); if (pendingItemSet) { context.ui.setPendingItem(null); } - context.ui.addItem( - { - type: MessageType.ERROR, - text: `Failed to reload skills: ${error instanceof Error ? error.message : String(error)}`, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: `Failed to reload skills: ${error instanceof Error ? error.message : String(error)}`, + }); } } diff --git a/packages/cli/src/ui/commands/statsCommand.test.ts b/packages/cli/src/ui/commands/statsCommand.test.ts index 2a054ecc4d..cf948790d6 100644 --- a/packages/cli/src/ui/commands/statsCommand.test.ts +++ b/packages/cli/src/ui/commands/statsCommand.test.ts @@ -37,13 +37,10 @@ describe('statsCommand', () => { const expectedDuration = formatDuration( endTime.getTime() - startTime.getTime(), ); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.STATS, - duration: expectedDuration, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.STATS, + duration: expectedDuration, + }); }); it('should fetch and display quota if config is available', async () => { @@ -62,7 +59,6 @@ describe('statsCommand', () => { expect.objectContaining({ quotas: mockQuota, }), - expect.any(Number), ); }); @@ -75,12 +71,9 @@ describe('statsCommand', () => { // eslint-disable-next-line @typescript-eslint/no-floating-promises modelSubCommand.action(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.MODEL_STATS, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.MODEL_STATS, + }); }); it('should display tool stats when using the "tools" subcommand', () => { @@ -92,11 +85,8 @@ describe('statsCommand', () => { // eslint-disable-next-line @typescript-eslint/no-floating-promises toolsSubCommand.action(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.TOOL_STATS, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.TOOL_STATS, + }); }); }); diff --git a/packages/cli/src/ui/commands/statsCommand.ts b/packages/cli/src/ui/commands/statsCommand.ts index 718da86f69..917c52c143 100644 --- a/packages/cli/src/ui/commands/statsCommand.ts +++ b/packages/cli/src/ui/commands/statsCommand.ts @@ -17,13 +17,10 @@ async function defaultSessionView(context: CommandContext) { const now = new Date(); const { sessionStartTime } = context.session.stats; if (!sessionStartTime) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: 'Session start time is unavailable, cannot calculate stats.', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: 'Session start time is unavailable, cannot calculate stats.', + }); return; } const wallDuration = now.getTime() - sessionStartTime.getTime(); @@ -40,7 +37,7 @@ async function defaultSessionView(context: CommandContext) { } } - context.ui.addItem(statsItem, Date.now()); + context.ui.addItem(statsItem); } export const statsCommand: SlashCommand = { @@ -68,12 +65,9 @@ export const statsCommand: SlashCommand = { kind: CommandKind.BUILT_IN, autoExecute: true, action: (context: CommandContext) => { - context.ui.addItem( - { - type: MessageType.MODEL_STATS, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.MODEL_STATS, + }); }, }, { @@ -82,12 +76,9 @@ export const statsCommand: SlashCommand = { kind: CommandKind.BUILT_IN, autoExecute: true, action: (context: CommandContext) => { - context.ui.addItem( - { - type: MessageType.TOOL_STATS, - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.TOOL_STATS, + }); }, }, ], diff --git a/packages/cli/src/ui/commands/toolsCommand.test.ts b/packages/cli/src/ui/commands/toolsCommand.test.ts index d44be3f973..257e6ba167 100644 --- a/packages/cli/src/ui/commands/toolsCommand.test.ts +++ b/packages/cli/src/ui/commands/toolsCommand.test.ts @@ -40,13 +40,10 @@ describe('toolsCommand', () => { if (!toolsCommand.action) throw new Error('Action not defined'); await toolsCommand.action(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Could not retrieve tool registry.', - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Could not retrieve tool registry.', + }); }); it('should display "No tools available" when none are found', async () => { @@ -63,14 +60,11 @@ describe('toolsCommand', () => { if (!toolsCommand.action) throw new Error('Action not defined'); await toolsCommand.action(mockContext, ''); - expect(mockContext.ui.addItem).toHaveBeenCalledWith( - { - type: MessageType.TOOLS_LIST, - tools: [], - showDescriptions: false, - }, - expect.any(Number), - ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith({ + type: MessageType.TOOLS_LIST, + tools: [], + showDescriptions: false, + }); }); it('should list tools without descriptions by default', async () => { diff --git a/packages/cli/src/ui/commands/toolsCommand.ts b/packages/cli/src/ui/commands/toolsCommand.ts index bbb86082f1..ff772c5cc8 100644 --- a/packages/cli/src/ui/commands/toolsCommand.ts +++ b/packages/cli/src/ui/commands/toolsCommand.ts @@ -27,13 +27,10 @@ export const toolsCommand: SlashCommand = { const toolRegistry = context.services.config?.getToolRegistry(); if (!toolRegistry) { - context.ui.addItem( - { - type: MessageType.ERROR, - text: 'Could not retrieve tool registry.', - }, - Date.now(), - ); + context.ui.addItem({ + type: MessageType.ERROR, + text: 'Could not retrieve tool registry.', + }); return; } @@ -51,6 +48,6 @@ export const toolsCommand: SlashCommand = { showDescriptions: useShowDescriptions, }; - context.ui.addItem(toolsListItem, Date.now()); + context.ui.addItem(toolsListItem); }, }; diff --git a/packages/cli/src/ui/components/MultiFolderTrustDialog.test.tsx b/packages/cli/src/ui/components/MultiFolderTrustDialog.test.tsx index 36ec622a65..64e992b06a 100644 --- a/packages/cli/src/ui/components/MultiFolderTrustDialog.test.tsx +++ b/packages/cli/src/ui/components/MultiFolderTrustDialog.test.tsx @@ -11,7 +11,7 @@ import { MultiFolderTrustChoice, type MultiFolderTrustDialogProps, } from './MultiFolderTrustDialog.js'; -import { vi } from 'vitest'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; import { TrustLevel, type LoadedTrustedFolders, @@ -213,13 +213,10 @@ describe('MultiFolderTrustDialog', () => { onSelect(MultiFolderTrustChoice.YES); }); - expect(mockAddItem).toHaveBeenCalledWith( - { - type: MessageType.ERROR, - text: 'Configuration is not available.', - }, - expect.any(Number), - ); + expect(mockAddItem).toHaveBeenCalledWith({ + type: MessageType.ERROR, + text: 'Configuration is not available.', + }); expect(mockOnComplete).toHaveBeenCalled(); expect(mockFinishAddingDirectories).not.toHaveBeenCalled(); }); diff --git a/packages/cli/src/ui/components/MultiFolderTrustDialog.tsx b/packages/cli/src/ui/components/MultiFolderTrustDialog.tsx index 1f0885358f..5928f766b7 100644 --- a/packages/cli/src/ui/components/MultiFolderTrustDialog.tsx +++ b/packages/cli/src/ui/components/MultiFolderTrustDialog.tsx @@ -31,13 +31,16 @@ export interface MultiFolderTrustDialogProps { config: Config, addItem: ( itemData: Omit, - baseTimestamp: number, + baseTimestamp?: number, ) => number, added: string[], errors: string[], ) => Promise; config: Config; - addItem: (itemData: Omit, baseTimestamp: number) => number; + addItem: ( + itemData: Omit, + baseTimestamp?: number, + ) => number; } export const MultiFolderTrustDialog: React.FC = ({ @@ -95,13 +98,10 @@ export const MultiFolderTrustDialog: React.FC = ({ setSubmitted(true); if (!config) { - addItem( - { - type: MessageType.ERROR, - text: 'Configuration is not available.', - }, - Date.now(), - ); + addItem({ + type: MessageType.ERROR, + text: 'Configuration is not available.', + }); onComplete(); return; } diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx index 5952508bf8..21bb2191e9 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx +++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx @@ -780,7 +780,6 @@ describe('useGeminiStream', () => { 'Agent execution stopped: Stop reason from hook', ), }), - expect.any(Number), ); // Ensure we do NOT call back to the API expect(mockSendMessageStream).not.toHaveBeenCalled(); @@ -1085,13 +1084,10 @@ describe('useGeminiStream', () => { // Verify cancellation message is added await waitFor(() => { - expect(mockAddItem).toHaveBeenCalledWith( - { - type: MessageType.INFO, - text: 'Request cancelled.', - }, - expect.any(Number), - ); + expect(mockAddItem).toHaveBeenCalledWith({ + type: MessageType.INFO, + text: 'Request cancelled.', + }); }); // Verify state is reset @@ -1194,7 +1190,6 @@ describe('useGeminiStream', () => { expect.objectContaining({ text: 'Request cancelled.', }), - expect.any(Number), ); }); @@ -1330,7 +1325,6 @@ describe('useGeminiStream', () => { expect.objectContaining({ text: 'Request cancelled.', }), - expect.any(Number), ); }); @@ -1995,13 +1989,10 @@ describe('useGeminiStream', () => { }); await waitFor(() => { - expect(mockAddItem).toHaveBeenCalledWith( - { - type: 'info', - text: expectedMessage, - }, - expect.any(Number), - ); + expect(mockAddItem).toHaveBeenCalledWith({ + type: 'info', + text: expectedMessage, + }); }); }, ); @@ -2644,13 +2635,10 @@ describe('useGeminiStream', () => { expect(result.current.loopDetectionConfirmationRequest).toBeNull(); // Verify appropriate message was added - expect(mockAddItem).toHaveBeenCalledWith( - { - type: 'info', - text: 'Loop detection has been disabled for this session. Retrying request...', - }, - expect.any(Number), - ); + expect(mockAddItem).toHaveBeenCalledWith({ + type: 'info', + text: 'Loop detection has been disabled for this session. Retrying request...', + }); // Verify that the request was retried await waitFor(() => { @@ -2707,13 +2695,10 @@ describe('useGeminiStream', () => { expect(result.current.loopDetectionConfirmationRequest).toBeNull(); // Verify appropriate message was added - expect(mockAddItem).toHaveBeenCalledWith( - { - type: 'info', - text: 'A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.', - }, - expect.any(Number), - ); + expect(mockAddItem).toHaveBeenCalledWith({ + type: 'info', + text: 'A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.', + }); // Verify that the request was NOT retried expect(mockSendMessageStream).toHaveBeenCalledTimes(1); @@ -2750,13 +2735,10 @@ describe('useGeminiStream', () => { expect(result.current.loopDetectionConfirmationRequest).toBeNull(); // Verify first message was added - expect(mockAddItem).toHaveBeenCalledWith( - { - type: 'info', - text: 'A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.', - }, - expect.any(Number), - ); + expect(mockAddItem).toHaveBeenCalledWith({ + type: 'info', + text: 'A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.', + }); // Second loop detection - set up fresh mock for second call mockSendMessageStream.mockReturnValueOnce( @@ -2800,13 +2782,10 @@ describe('useGeminiStream', () => { expect(result.current.loopDetectionConfirmationRequest).toBeNull(); // Verify second message was added - expect(mockAddItem).toHaveBeenCalledWith( - { - type: 'info', - text: 'Loop detection has been disabled for this session. Retrying request...', - }, - expect.any(Number), - ); + expect(mockAddItem).toHaveBeenCalledWith({ + type: 'info', + text: 'Loop detection has been disabled for this session. Retrying request...', + }); // Verify that the request was retried await waitFor(() => { diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index ec7370ccfa..253582359e 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -149,7 +149,6 @@ export const useGeminiStream = ( mapTrackedToolCallsToDisplay( completedToolCallsFromScheduler as TrackedToolCall[], ), - Date.now(), ); // Clear the live-updating display now that the final state is in history. @@ -248,10 +247,7 @@ export const useGeminiStream = ( prevActiveShellPtyIdRef.current !== null && activeShellPtyId === null ) { - addItem( - { type: MessageType.INFO, text: 'Request cancelled.' }, - Date.now(), - ); + addItem({ type: MessageType.INFO, text: 'Request cancelled.' }); setIsResponding(false); } prevActiveShellPtyIdRef.current = activeShellPtyId; @@ -351,12 +347,9 @@ export const useGeminiStream = ( } return tool; }); - addItem( - { ...toolGroup, tools: updatedTools } as HistoryItemWithoutId, - Date.now(), - ); + addItem({ ...toolGroup, tools: updatedTools } as HistoryItemWithoutId); } else { - addItem(pendingHistoryItemRef.current, Date.now()); + addItem(pendingHistoryItemRef.current); } } setPendingHistoryItem(null); @@ -368,13 +361,10 @@ export const useGeminiStream = ( // If shell is active, we delay this message to ensure correct ordering // (Shell item first, then Info message). if (!activeShellPtyId) { - addItem( - { - type: MessageType.INFO, - text: 'Request cancelled.', - }, - Date.now(), - ); + addItem({ + type: MessageType.INFO, + text: 'Request cancelled.', + }); setIsResponding(false); } } @@ -719,32 +709,26 @@ export const useGeminiStream = ( addItem(pendingHistoryItemRef.current, userMessageTimestamp); setPendingHistoryItem(null); } - return addItem( - { - type: 'info', - text: - `IMPORTANT: This conversation exceeded the compress threshold. ` + - `A compressed context will be sent for future messages (compressed from: ` + - `${eventValue?.originalTokenCount ?? 'unknown'} to ` + - `${eventValue?.newTokenCount ?? 'unknown'} tokens).`, - }, - Date.now(), - ); + return addItem({ + type: 'info', + text: + `IMPORTANT: This conversation exceeded the compress threshold. ` + + `A compressed context will be sent for future messages (compressed from: ` + + `${eventValue?.originalTokenCount ?? 'unknown'} to ` + + `${eventValue?.newTokenCount ?? 'unknown'} tokens).`, + }); }, [addItem, pendingHistoryItemRef, setPendingHistoryItem], ); const handleMaxSessionTurnsEvent = useCallback( () => - addItem( - { - type: 'info', - text: - `The session has reached the maximum number of turns: ${config.getMaxSessionTurns()}. ` + - `Please update this limit in your setting.json file.`, - }, - Date.now(), - ), + addItem({ + type: 'info', + text: + `The session has reached the maximum number of turns: ${config.getMaxSessionTurns()}. ` + + `Please update this limit in your setting.json file.`, + }), [addItem, config], ); @@ -764,13 +748,10 @@ export const useGeminiStream = ( ' Please try reducing the size of your message or use the `/compress` command to compress the chat history.'; } - addItem( - { - type: 'info', - text, - }, - Date.now(), - ); + addItem({ + type: 'info', + text, + }); }, [addItem, onCancelSubmit, config], ); @@ -1041,13 +1022,10 @@ export const useGeminiStream = ( .getGeminiClient() .getLoopDetectionService() .disableForSession(); - addItem( - { - type: 'info', - text: `Loop detection has been disabled for this session. Retrying request...`, - }, - Date.now(), - ); + addItem({ + type: 'info', + text: `Loop detection has been disabled for this session. Retrying request...`, + }); if (lastQueryRef.current && lastPromptIdRef.current) { // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -1058,13 +1036,10 @@ export const useGeminiStream = ( ); } } else { - addItem( - { - type: 'info', - text: `A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.`, - }, - Date.now(), - ); + addItem({ + type: 'info', + text: `A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.`, + }); } }, }); @@ -1215,13 +1190,10 @@ export const useGeminiStream = ( ); if (stopExecutionTool && stopExecutionTool.response.error) { - addItem( - { - type: MessageType.INFO, - text: `Agent execution stopped: ${stopExecutionTool.response.error.message}`, - }, - Date.now(), - ); + addItem({ + type: MessageType.INFO, + text: `Agent execution stopped: ${stopExecutionTool.response.error.message}`, + }); setIsResponding(false); const callIdsToMarkAsSubmitted = geminiTools.map( @@ -1240,13 +1212,10 @@ export const useGeminiStream = ( // If the turn was cancelled via the imperative escape key flow, // the cancellation message is added there. We check the ref to avoid duplication. if (!turnCancelledRef.current) { - addItem( - { - type: MessageType.INFO, - text: 'Request cancelled.', - }, - Date.now(), - ); + addItem({ + type: MessageType.INFO, + text: 'Request cancelled.', + }); } setIsResponding(false); diff --git a/packages/cli/src/ui/hooks/useHistoryManager.test.ts b/packages/cli/src/ui/hooks/useHistoryManager.test.ts index cff7ef69bf..79a708ec41 100644 --- a/packages/cli/src/ui/hooks/useHistoryManager.test.ts +++ b/packages/cli/src/ui/hooks/useHistoryManager.test.ts @@ -200,4 +200,23 @@ describe('useHistoryManager', () => { expect(result.current.history[1].text).toBe('Gemini response'); expect(result.current.history[2].text).toBe('Message 1'); }); + + it('should use Date.now() as default baseTimestamp if not provided', () => { + const { result } = renderHook(() => useHistory()); + const before = Date.now(); + const itemData: Omit = { + type: 'user', + text: 'Default timestamp test', + }; + + act(() => { + result.current.addItem(itemData); + }); + const after = Date.now(); + + expect(result.current.history).toHaveLength(1); + // ID should be >= before + 1 (since counter starts at 0 and increments to 1) + expect(result.current.history[0].id).toBeGreaterThanOrEqual(before + 1); + expect(result.current.history[0].id).toBeLessThanOrEqual(after + 1); + }); }); diff --git a/packages/cli/src/ui/hooks/useHistoryManager.ts b/packages/cli/src/ui/hooks/useHistoryManager.ts index 66eff02824..3c7abaacc6 100644 --- a/packages/cli/src/ui/hooks/useHistoryManager.ts +++ b/packages/cli/src/ui/hooks/useHistoryManager.ts @@ -17,7 +17,7 @@ export interface UseHistoryManagerReturn { history: HistoryItem[]; addItem: ( itemData: Omit, - baseTimestamp: number, + baseTimestamp?: number, isResuming?: boolean, ) => number; // Returns the generated ID updateItem: ( @@ -56,7 +56,7 @@ export function useHistory({ const addItem = useCallback( ( itemData: Omit, - baseTimestamp: number, + baseTimestamp: number = Date.now(), isResuming: boolean = false, ): number => { const id = getNextMessageId(baseTimestamp); diff --git a/packages/cli/src/ui/hooks/useIncludeDirsTrust.test.tsx b/packages/cli/src/ui/hooks/useIncludeDirsTrust.test.tsx index 1954301c2a..199e1b4587 100644 --- a/packages/cli/src/ui/hooks/useIncludeDirsTrust.test.tsx +++ b/packages/cli/src/ui/hooks/useIncludeDirsTrust.test.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; import type { Mock } from 'vitest'; import { renderHook } from '../../test-utils/render.js'; import { waitFor } from '../../test-utils/async.js'; @@ -132,7 +132,6 @@ describe('useIncludeDirsTrust', () => { expect.objectContaining({ text: expect.stringContaining("Error adding '/dir2': Test error"), }), - expect.any(Number), ); expect( mockConfig.clearPendingIncludeDirectories, diff --git a/packages/cli/src/ui/hooks/useIncludeDirsTrust.tsx b/packages/cli/src/ui/hooks/useIncludeDirsTrust.tsx index 53f613898b..90cb33cb1a 100644 --- a/packages/cli/src/ui/hooks/useIncludeDirsTrust.tsx +++ b/packages/cli/src/ui/hooks/useIncludeDirsTrust.tsx @@ -18,18 +18,18 @@ import { MessageType, type HistoryItem } from '../types.js'; async function finishAddingDirectories( config: Config, - addItem: (itemData: Omit, baseTimestamp: number) => number, + addItem: ( + itemData: Omit, + baseTimestamp?: number, + ) => number, added: string[], errors: string[], ) { if (!config) { - addItem( - { - type: MessageType.ERROR, - text: 'Configuration is not available.', - }, - Date.now(), - ); + addItem({ + type: MessageType.ERROR, + text: 'Configuration is not available.', + }); return; } @@ -49,7 +49,7 @@ async function finishAddingDirectories( } if (errors.length > 0) { - addItem({ type: MessageType.ERROR, text: errors.join('\n') }, Date.now()); + addItem({ type: MessageType.ERROR, text: errors.join('\n') }); } }