mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-16 13:00:39 -07:00
Merge branch 'main' into fix/headless-log
This commit is contained in:
@@ -85,6 +85,7 @@ import {
|
||||
buildUserSteeringHintPrompt,
|
||||
logBillingEvent,
|
||||
ApiKeyUpdatedEvent,
|
||||
type InjectionSource,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { validateAuthMethod } from '../config/auth.js';
|
||||
import process from 'node:process';
|
||||
@@ -1089,13 +1090,16 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const hintListener = (hint: string) => {
|
||||
pendingHintsRef.current.push(hint);
|
||||
const hintListener = (text: string, source: InjectionSource) => {
|
||||
if (source !== 'user_steering') {
|
||||
return;
|
||||
}
|
||||
pendingHintsRef.current.push(text);
|
||||
setPendingHintCount((prev) => prev + 1);
|
||||
};
|
||||
config.userHintService.onUserHint(hintListener);
|
||||
config.injectionService.onInjection(hintListener);
|
||||
return () => {
|
||||
config.userHintService.offUserHint(hintListener);
|
||||
config.injectionService.offInjection(hintListener);
|
||||
};
|
||||
}, [config]);
|
||||
|
||||
@@ -1259,7 +1263,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
config.userHintService.addUserHint(trimmed);
|
||||
config.injectionService.addInjection(trimmed, 'user_steering');
|
||||
// Render hints with a distinct style.
|
||||
historyManager.addItem({
|
||||
type: 'hint',
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('clearCommand', () => {
|
||||
fireSessionEndEvent: vi.fn().mockResolvedValue(undefined),
|
||||
fireSessionStartEvent: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
userHintService: {
|
||||
injectionService: {
|
||||
clear: mockHintClear,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@ export const clearCommand: SlashCommand = {
|
||||
}
|
||||
|
||||
// Reset user steering hints
|
||||
config?.userHintService.clear();
|
||||
config?.injectionService.clear();
|
||||
|
||||
// Start a new conversation recording with a new session ID
|
||||
// We MUST do this before calling resetChat() so the new ChatRecordingService
|
||||
|
||||
@@ -66,6 +66,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: Array.from({ length: 10 }, (_, i) => `mcp${i}`),
|
||||
hooks: Array.from({ length: 10 }, (_, i) => `hook${i}`),
|
||||
skills: Array.from({ length: 10 }, (_, i) => `skill${i}`),
|
||||
agents: [],
|
||||
settings: Array.from({ length: 10 }, (_, i) => `setting${i}`),
|
||||
discoveryErrors: [],
|
||||
securityWarnings: [],
|
||||
@@ -95,6 +96,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: [],
|
||||
hooks: [],
|
||||
skills: [],
|
||||
agents: [],
|
||||
settings: [],
|
||||
discoveryErrors: [],
|
||||
securityWarnings: [],
|
||||
@@ -125,6 +127,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: [],
|
||||
hooks: [],
|
||||
skills: [],
|
||||
agents: [],
|
||||
settings: [],
|
||||
discoveryErrors: [],
|
||||
securityWarnings: [],
|
||||
@@ -152,6 +155,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: [],
|
||||
hooks: [],
|
||||
skills: [],
|
||||
agents: [],
|
||||
settings: [],
|
||||
discoveryErrors: [],
|
||||
securityWarnings: [],
|
||||
@@ -332,6 +336,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: ['mcp1'],
|
||||
hooks: ['hook1'],
|
||||
skills: ['skill1'],
|
||||
agents: ['agent1'],
|
||||
settings: ['general', 'ui'],
|
||||
discoveryErrors: [],
|
||||
securityWarnings: [],
|
||||
@@ -355,6 +360,8 @@ describe('FolderTrustDialog', () => {
|
||||
expect(lastFrame()).toContain('- hook1');
|
||||
expect(lastFrame()).toContain('• Skills (1):');
|
||||
expect(lastFrame()).toContain('- skill1');
|
||||
expect(lastFrame()).toContain('• Agents (1):');
|
||||
expect(lastFrame()).toContain('- agent1');
|
||||
expect(lastFrame()).toContain('• Setting overrides (2):');
|
||||
expect(lastFrame()).toContain('- general');
|
||||
expect(lastFrame()).toContain('- ui');
|
||||
@@ -367,6 +374,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: [],
|
||||
hooks: [],
|
||||
skills: [],
|
||||
agents: [],
|
||||
settings: [],
|
||||
discoveryErrors: [],
|
||||
securityWarnings: ['Dangerous setting detected!'],
|
||||
@@ -390,6 +398,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: [],
|
||||
hooks: [],
|
||||
skills: [],
|
||||
agents: [],
|
||||
settings: [],
|
||||
discoveryErrors: ['Failed to load custom commands'],
|
||||
securityWarnings: [],
|
||||
@@ -413,6 +422,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: [],
|
||||
hooks: [],
|
||||
skills: [],
|
||||
agents: [],
|
||||
settings: [],
|
||||
discoveryErrors: [],
|
||||
securityWarnings: [],
|
||||
@@ -446,6 +456,7 @@ describe('FolderTrustDialog', () => {
|
||||
mcps: [`${ansiRed}mcp-with-ansi${ansiReset}`],
|
||||
hooks: [`${ansiRed}hook-with-ansi${ansiReset}`],
|
||||
skills: [`${ansiRed}skill-with-ansi${ansiReset}`],
|
||||
agents: [],
|
||||
settings: [`${ansiRed}setting-with-ansi${ansiReset}`],
|
||||
discoveryErrors: [`${ansiRed}error-with-ansi${ansiReset}`],
|
||||
securityWarnings: [`${ansiRed}warning-with-ansi${ansiReset}`],
|
||||
|
||||
@@ -135,6 +135,7 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
|
||||
{ label: 'MCP Servers', items: discoveryResults?.mcps ?? [] },
|
||||
{ label: 'Hooks', items: discoveryResults?.hooks ?? [] },
|
||||
{ label: 'Skills', items: discoveryResults?.skills ?? [] },
|
||||
{ label: 'Agents', items: discoveryResults?.agents ?? [] },
|
||||
{ label: 'Setting overrides', items: discoveryResults?.settings ?? [] },
|
||||
].filter((g) => g.items.length > 0);
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ import {
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
|
||||
AuthType,
|
||||
UserTierId,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Config, ModelSlashCommandEvent } from '@google/gemini-cli-core';
|
||||
|
||||
@@ -28,8 +30,9 @@ const mockGetDisplayString = vi.fn();
|
||||
const mockLogModelSlashCommand = vi.fn();
|
||||
const mockModelSlashCommandEvent = vi.fn();
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async () => {
|
||||
const actual = await vi.importActual('@google/gemini-cli-core');
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
getDisplayString: (val: string) => mockGetDisplayString(val),
|
||||
@@ -40,6 +43,7 @@ vi.mock('@google/gemini-cli-core', async () => {
|
||||
mockModelSlashCommandEvent(model);
|
||||
}
|
||||
},
|
||||
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL: 'gemini-3.1-flash-lite-preview',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -49,6 +53,9 @@ describe('<ModelDialog />', () => {
|
||||
const mockOnClose = vi.fn();
|
||||
const mockGetHasAccessToPreviewModel = vi.fn();
|
||||
const mockGetGemini31LaunchedSync = vi.fn();
|
||||
const mockGetProModelNoAccess = vi.fn();
|
||||
const mockGetProModelNoAccessSync = vi.fn();
|
||||
const mockGetUserTier = vi.fn();
|
||||
|
||||
interface MockConfig extends Partial<Config> {
|
||||
setModel: (model: string, isTemporary?: boolean) => void;
|
||||
@@ -56,6 +63,9 @@ describe('<ModelDialog />', () => {
|
||||
getHasAccessToPreviewModel: () => boolean;
|
||||
getIdeMode: () => boolean;
|
||||
getGemini31LaunchedSync: () => boolean;
|
||||
getProModelNoAccess: () => Promise<boolean>;
|
||||
getProModelNoAccessSync: () => boolean;
|
||||
getUserTier: () => UserTierId | undefined;
|
||||
}
|
||||
|
||||
const mockConfig: MockConfig = {
|
||||
@@ -64,6 +74,9 @@ describe('<ModelDialog />', () => {
|
||||
getHasAccessToPreviewModel: mockGetHasAccessToPreviewModel,
|
||||
getIdeMode: () => false,
|
||||
getGemini31LaunchedSync: mockGetGemini31LaunchedSync,
|
||||
getProModelNoAccess: mockGetProModelNoAccess,
|
||||
getProModelNoAccessSync: mockGetProModelNoAccessSync,
|
||||
getUserTier: mockGetUserTier,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -71,6 +84,9 @@ describe('<ModelDialog />', () => {
|
||||
mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO);
|
||||
mockGetHasAccessToPreviewModel.mockReturnValue(false);
|
||||
mockGetGemini31LaunchedSync.mockReturnValue(false);
|
||||
mockGetProModelNoAccess.mockResolvedValue(false);
|
||||
mockGetProModelNoAccessSync.mockReturnValue(false);
|
||||
mockGetUserTier.mockReturnValue(UserTierId.STANDARD);
|
||||
|
||||
// Default implementation for getDisplayString
|
||||
mockGetDisplayString.mockImplementation((val: string) => {
|
||||
@@ -109,6 +125,55 @@ describe('<ModelDialog />', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders the "manual" view initially for users with no pro access and filters Pro models with correct order', async () => {
|
||||
mockGetProModelNoAccessSync.mockReturnValue(true);
|
||||
mockGetProModelNoAccess.mockResolvedValue(true);
|
||||
mockGetHasAccessToPreviewModel.mockReturnValue(true);
|
||||
mockGetUserTier.mockReturnValue(UserTierId.FREE);
|
||||
mockGetDisplayString.mockImplementation((val: string) => val);
|
||||
|
||||
const { lastFrame, unmount } = await renderComponent();
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Select Model');
|
||||
expect(output).not.toContain(DEFAULT_GEMINI_MODEL);
|
||||
expect(output).not.toContain(PREVIEW_GEMINI_MODEL);
|
||||
|
||||
// Verify order: Flash Preview -> Flash Lite Preview -> Flash -> Flash Lite
|
||||
const flashPreviewIdx = output.indexOf(PREVIEW_GEMINI_FLASH_MODEL);
|
||||
const flashLitePreviewIdx = output.indexOf(
|
||||
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
|
||||
);
|
||||
const flashIdx = output.indexOf(DEFAULT_GEMINI_FLASH_MODEL);
|
||||
const flashLiteIdx = output.indexOf(DEFAULT_GEMINI_FLASH_LITE_MODEL);
|
||||
|
||||
expect(flashPreviewIdx).toBeLessThan(flashLitePreviewIdx);
|
||||
expect(flashLitePreviewIdx).toBeLessThan(flashIdx);
|
||||
expect(flashIdx).toBeLessThan(flashLiteIdx);
|
||||
|
||||
expect(output).not.toContain('Auto');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('closes dialog on escape in "manual" view for users with no pro access', async () => {
|
||||
mockGetProModelNoAccessSync.mockReturnValue(true);
|
||||
mockGetProModelNoAccess.mockResolvedValue(true);
|
||||
const { stdin, waitUntilReady, unmount } = await renderComponent();
|
||||
|
||||
// Already in manual view
|
||||
await act(async () => {
|
||||
stdin.write('\u001B'); // Escape
|
||||
});
|
||||
await act(async () => {
|
||||
await waitUntilReady();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('switches to "manual" view when "Manual" is selected and uses getDisplayString for models', async () => {
|
||||
mockGetDisplayString.mockImplementation((val: string) => {
|
||||
if (val === DEFAULT_GEMINI_MODEL) return 'Formatted Pro Model';
|
||||
@@ -369,5 +434,50 @@ describe('<ModelDialog />', () => {
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('hides Flash Lite Preview model for users with pro access', async () => {
|
||||
mockGetProModelNoAccessSync.mockReturnValue(false);
|
||||
mockGetProModelNoAccess.mockResolvedValue(false);
|
||||
mockGetHasAccessToPreviewModel.mockReturnValue(true);
|
||||
const { lastFrame, stdin, waitUntilReady, unmount } =
|
||||
await renderComponent();
|
||||
|
||||
// Go to manual view
|
||||
await act(async () => {
|
||||
stdin.write('\u001B[B'); // Manual
|
||||
});
|
||||
await waitUntilReady();
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
});
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).not.toContain(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL);
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('shows Flash Lite Preview model for free tier users', async () => {
|
||||
mockGetProModelNoAccessSync.mockReturnValue(false);
|
||||
mockGetProModelNoAccess.mockResolvedValue(false);
|
||||
mockGetHasAccessToPreviewModel.mockReturnValue(true);
|
||||
mockGetUserTier.mockReturnValue(UserTierId.FREE);
|
||||
const { lastFrame, stdin, waitUntilReady, unmount } =
|
||||
await renderComponent();
|
||||
|
||||
// Go to manual view
|
||||
await act(async () => {
|
||||
stdin.write('\u001B[B'); // Manual
|
||||
});
|
||||
await waitUntilReady();
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
});
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL);
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { useCallback, useContext, useMemo, useState, useEffect } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import {
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_FLASH_MODEL,
|
||||
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
@@ -21,6 +22,8 @@ import {
|
||||
getDisplayString,
|
||||
AuthType,
|
||||
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||
isProModel,
|
||||
UserTierId,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
@@ -35,9 +38,26 @@ interface ModelDialogProps {
|
||||
export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
const config = useContext(ConfigContext);
|
||||
const settings = useSettings();
|
||||
const [view, setView] = useState<'main' | 'manual'>('main');
|
||||
const [hasAccessToProModel, setHasAccessToProModel] = useState<boolean>(
|
||||
() => !(config?.getProModelNoAccessSync() ?? false),
|
||||
);
|
||||
const [view, setView] = useState<'main' | 'manual'>(() =>
|
||||
config?.getProModelNoAccessSync() ? 'manual' : 'main',
|
||||
);
|
||||
const [persistMode, setPersistMode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function checkAccess() {
|
||||
if (!config) return;
|
||||
const noAccess = await config.getProModelNoAccess();
|
||||
setHasAccessToProModel(!noAccess);
|
||||
if (noAccess) {
|
||||
setView('manual');
|
||||
}
|
||||
}
|
||||
void checkAccess();
|
||||
}, [config]);
|
||||
|
||||
// Determine the Preferred Model (read once when the dialog opens).
|
||||
const preferredModel = config?.getModel() || DEFAULT_GEMINI_MODEL_AUTO;
|
||||
|
||||
@@ -66,7 +86,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (key.name === 'escape') {
|
||||
if (view === 'manual') {
|
||||
if (view === 'manual' && hasAccessToProModel) {
|
||||
setView('main');
|
||||
} else {
|
||||
onClose();
|
||||
@@ -115,6 +135,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
}, [shouldShowPreviewModels, manualModelSelected, useGemini31]);
|
||||
|
||||
const manualOptions = useMemo(() => {
|
||||
const isFreeTier = config?.getUserTier() === UserTierId.FREE;
|
||||
const list = [
|
||||
{
|
||||
value: DEFAULT_GEMINI_MODEL,
|
||||
@@ -142,7 +163,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
? PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL
|
||||
: previewProModel;
|
||||
|
||||
list.unshift(
|
||||
const previewOptions = [
|
||||
{
|
||||
value: previewProValue,
|
||||
title: getDisplayString(previewProModel),
|
||||
@@ -153,10 +174,32 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
title: getDisplayString(PREVIEW_GEMINI_FLASH_MODEL),
|
||||
key: PREVIEW_GEMINI_FLASH_MODEL,
|
||||
},
|
||||
);
|
||||
];
|
||||
|
||||
if (isFreeTier) {
|
||||
previewOptions.push({
|
||||
value: PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
|
||||
title: getDisplayString(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL),
|
||||
key: PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
|
||||
});
|
||||
}
|
||||
|
||||
list.unshift(...previewOptions);
|
||||
}
|
||||
|
||||
if (!hasAccessToProModel) {
|
||||
// Filter out all Pro models for free tier
|
||||
return list.filter((option) => !isProModel(option.value));
|
||||
}
|
||||
|
||||
return list;
|
||||
}, [shouldShowPreviewModels, useGemini31, useCustomToolModel]);
|
||||
}, [
|
||||
shouldShowPreviewModels,
|
||||
useGemini31,
|
||||
useCustomToolModel,
|
||||
hasAccessToProModel,
|
||||
config,
|
||||
]);
|
||||
|
||||
const options = view === 'main' ? mainOptions : manualOptions;
|
||||
|
||||
|
||||
-4
@@ -13,10 +13,6 @@ Tips for getting started:
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? confirming_tool Confirming tool description │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
Action Required (was prompted):
|
||||
|
||||
|
||||
@@ -118,10 +118,30 @@ describe('<ToolGroupMessage />', () => {
|
||||
{ config: baseMockConfig, settings: fullVerbositySettings },
|
||||
);
|
||||
|
||||
// Should now render confirming tools
|
||||
// Should now hide confirming tools (to avoid duplication with Global Queue)
|
||||
await waitUntilReady();
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders canceled tool calls', async () => {
|
||||
const toolCalls = [
|
||||
createToolCall({
|
||||
callId: 'canceled-tool',
|
||||
name: 'canceled-tool',
|
||||
status: CoreToolCallStatus.Cancelled,
|
||||
}),
|
||||
];
|
||||
const item = createItem(toolCalls);
|
||||
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{ config: baseMockConfig, settings: fullVerbositySettings },
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('test-tool');
|
||||
expect(output).toMatchSnapshot('canceled_tool');
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -842,7 +862,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
expect(lastFrame({ allowEmpty: true })).not.toBe('');
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
unmount();
|
||||
});
|
||||
|
||||
|
||||
@@ -110,11 +110,12 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
||||
() =>
|
||||
toolCalls.filter((t) => {
|
||||
const displayStatus = mapCoreStatusToDisplayStatus(t.status);
|
||||
// We used to filter out Pending and Confirming statuses here to avoid
|
||||
// duplication with the Global Queue, but this causes tools to appear to
|
||||
// "vanish" from the context after approval.
|
||||
// We now allow them to be visible here as well.
|
||||
return displayStatus !== ToolCallStatus.Canceled;
|
||||
// We hide Confirming tools from the history log because they are
|
||||
// currently being rendered in the interactive ToolConfirmationQueue.
|
||||
// We show everything else, including Pending (waiting to run) and
|
||||
// Canceled (rejected by user), to ensure the history is complete
|
||||
// and to avoid tools "vanishing" after approval.
|
||||
return displayStatus !== ToolCallStatus.Confirming;
|
||||
}),
|
||||
|
||||
[toolCalls],
|
||||
|
||||
@@ -49,6 +49,15 @@ exports[`<ToolGroupMessage /> > Border Color Logic > uses yellow border for shel
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<ToolGroupMessage /> > Golden Snapshots > renders canceled tool calls > canceled_tool 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────╮
|
||||
│ - canceled-tool A tool for testing │
|
||||
│ │
|
||||
│ Test result │
|
||||
╰──────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<ToolGroupMessage /> > Golden Snapshots > renders empty tool calls array 1`] = `""`;
|
||||
|
||||
exports[`<ToolGroupMessage /> > Golden Snapshots > renders header when scrolled 1`] = `
|
||||
|
||||
@@ -101,12 +101,13 @@ export const useExtensionUpdates = (
|
||||
return !currentState || currentState === ExtensionUpdateState.UNKNOWN;
|
||||
});
|
||||
if (extensionsToCheck.length === 0) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
checkForAllExtensionUpdates(
|
||||
void checkForAllExtensionUpdates(
|
||||
extensionsToCheck,
|
||||
extensionManager,
|
||||
dispatchExtensionStateUpdate,
|
||||
);
|
||||
).catch((e) => {
|
||||
debugLogger.warn(getErrorMessage(e));
|
||||
});
|
||||
}, [
|
||||
extensions,
|
||||
extensionManager,
|
||||
@@ -202,12 +203,18 @@ export const useExtensionUpdates = (
|
||||
);
|
||||
}
|
||||
if (scheduledUpdate) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Promise.all(updatePromises).then((results) => {
|
||||
const nonNullResults = results.filter((result) => result != null);
|
||||
void Promise.allSettled(updatePromises).then((results) => {
|
||||
const successfulUpdates = results
|
||||
.filter(
|
||||
(r): r is PromiseFulfilledResult<ExtensionUpdateInfo | undefined> =>
|
||||
r.status === 'fulfilled',
|
||||
)
|
||||
.map((r) => r.value)
|
||||
.filter((v): v is ExtensionUpdateInfo => v !== undefined);
|
||||
|
||||
scheduledUpdate.onCompleteCallbacks.forEach((callback) => {
|
||||
try {
|
||||
callback(nonNullResults);
|
||||
callback(successfulUpdates);
|
||||
} catch (e) {
|
||||
debugLogger.warn(getErrorMessage(e));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user