mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-09 01:27:41 -07:00
feat: add offline/hybrid mode with cloud subagent delegation
This commit is contained in:
@@ -34,6 +34,7 @@ describe('<StatusRow />', () => {
|
||||
contextFileNames: [],
|
||||
showApprovalModeIndicator: ApprovalMode.DEFAULT,
|
||||
allowPlanMode: false,
|
||||
isOfflineMode: false,
|
||||
renderMarkdown: true,
|
||||
currentModel: 'gemini-3',
|
||||
};
|
||||
@@ -140,4 +141,38 @@ describe('<StatusRow />', () => {
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('Tip: Test Tip');
|
||||
});
|
||||
|
||||
it('renders offline mode indicator in detailed UI', async () => {
|
||||
(useComposerStatus as Mock).mockReturnValue({
|
||||
isInteractiveShellWaiting: false,
|
||||
showLoadingIndicator: false,
|
||||
showTips: false,
|
||||
showWit: false,
|
||||
modeContentObj: null,
|
||||
showMinimalContext: false,
|
||||
});
|
||||
|
||||
const uiState: Partial<UIState> = {
|
||||
...defaultUiState,
|
||||
isOfflineMode: true,
|
||||
};
|
||||
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<StatusRow
|
||||
showUiDetails={true}
|
||||
isNarrow={false}
|
||||
terminalWidth={100}
|
||||
hideContextSummary={false}
|
||||
hideUiDetailsForSuggestions={false}
|
||||
hasPendingActionRequired={false}
|
||||
/>,
|
||||
{
|
||||
width: 100,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('offline');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -411,6 +411,11 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
<RawMarkdownIndicator />
|
||||
</Box>
|
||||
)}
|
||||
{uiState.isOfflineMode && (
|
||||
<Box marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||
<Text color={theme.status.success}>● offline</Text>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
showRow2Minimal &&
|
||||
|
||||
@@ -97,6 +97,33 @@ describe('ToolConfirmationMessage', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should use allow/always allow/deny labels for cloud-subagent confirmations', async () => {
|
||||
const confirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'info',
|
||||
title: 'Delegate to cloud-subagent',
|
||||
prompt:
|
||||
'Delegating to cloud-subagent for cloud execution.\nReason: Complex task.\nTask: Analyze migration risks.',
|
||||
};
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<ToolConfirmationMessage
|
||||
callId="test-call-id"
|
||||
confirmationDetails={confirmationDetails}
|
||||
config={mockConfig}
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="cloud-subagent"
|
||||
/>,
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('1. Allow');
|
||||
expect(output).toContain('2. Always allow');
|
||||
expect(output).toContain('3. Deny (esc)');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display WarningMessage for deceptive URLs in info type', async () => {
|
||||
const confirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'info',
|
||||
|
||||
@@ -371,29 +371,44 @@ export const ToolConfirmationMessage: React.FC<
|
||||
key: 'No, suggest changes (esc)',
|
||||
});
|
||||
} else if (confirmationDetails.type === 'info') {
|
||||
const isCloudSubagentConfirmation =
|
||||
toolName === 'cloud-subagent' || toolName === 'cloud_subagent';
|
||||
|
||||
options.push({
|
||||
label: 'Allow once',
|
||||
label: isCloudSubagentConfirmation ? 'Allow' : 'Allow once',
|
||||
value: ToolConfirmationOutcome.ProceedOnce,
|
||||
key: 'Allow once',
|
||||
key: isCloudSubagentConfirmation ? 'Allow' : 'Allow once',
|
||||
});
|
||||
if (isTrustedFolder) {
|
||||
options.push({
|
||||
label: 'Allow for this session',
|
||||
label: isCloudSubagentConfirmation
|
||||
? 'Always allow'
|
||||
: 'Allow for this session',
|
||||
value: ToolConfirmationOutcome.ProceedAlways,
|
||||
key: 'Allow for this session',
|
||||
key: isCloudSubagentConfirmation
|
||||
? 'Always allow'
|
||||
: 'Allow for this session',
|
||||
});
|
||||
if (allowPermanentApproval) {
|
||||
options.push({
|
||||
label: 'Allow for all future sessions',
|
||||
label: isCloudSubagentConfirmation
|
||||
? 'Always allow for all future sessions'
|
||||
: 'Allow for all future sessions',
|
||||
value: ToolConfirmationOutcome.ProceedAlwaysAndSave,
|
||||
key: 'Allow for all future sessions',
|
||||
key: isCloudSubagentConfirmation
|
||||
? 'Always allow for all future sessions'
|
||||
: 'Allow for all future sessions',
|
||||
});
|
||||
}
|
||||
}
|
||||
options.push({
|
||||
label: 'No, suggest changes (esc)',
|
||||
label: isCloudSubagentConfirmation
|
||||
? 'Deny (esc)'
|
||||
: 'No, suggest changes (esc)',
|
||||
value: ToolConfirmationOutcome.Cancel,
|
||||
key: 'No, suggest changes (esc)',
|
||||
key: isCloudSubagentConfirmation
|
||||
? 'Deny (esc)'
|
||||
: 'No, suggest changes (esc)',
|
||||
});
|
||||
} else if (confirmationDetails.type === 'mcp') {
|
||||
options.push({
|
||||
@@ -433,6 +448,7 @@ export const ToolConfirmationMessage: React.FC<
|
||||
allowPermanentApproval,
|
||||
config,
|
||||
isDiffingEnabled,
|
||||
toolName,
|
||||
]);
|
||||
|
||||
const availableBodyContentHeight = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user