feat: add offline/hybrid mode with cloud subagent delegation

This commit is contained in:
Samee Zahid
2026-04-15 00:04:48 -07:00
parent fd481ffc25
commit 27f35c3358
27 changed files with 927 additions and 23 deletions
@@ -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(() => {