diff --git a/packages/cli/src/ui/components/BtwDisplay.test.tsx b/packages/cli/src/ui/components/BtwDisplay.test.tsx
index 88fe96db74..4ae5719bfe 100644
--- a/packages/cli/src/ui/components/BtwDisplay.test.tsx
+++ b/packages/cli/src/ui/components/BtwDisplay.test.tsx
@@ -10,6 +10,12 @@ import { BtwDisplay } from './BtwDisplay.js';
import { StreamingState } from '../types.js';
import type { UIState } from '../contexts/UIStateContext.js';
+import { Text } from 'ink';
+
+vi.mock('./GeminiRespondingSpinner.js', () => ({
+ GeminiRespondingSpinner: () => ⠋,
+}));
+
describe('BtwDisplay', () => {
const defaultMockUiState = {
renderMarkdown: true,
diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx
index b274d0decf..4ca133af30 100644
--- a/packages/cli/src/ui/components/InputPrompt.test.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.test.tsx
@@ -5182,6 +5182,64 @@ describe('InputPrompt', () => {
});
unmount();
});
+
+ it('dismisses Btw and accepts input on typing when not streaming', async () => {
+ const dismissBtw = vi.fn();
+ const { stdin, stdout, unmount } = await renderWithProviders(
+ ,
+ {
+ uiState: {
+ btwState: {
+ isActive: true,
+ query: '',
+ response: '',
+ isStreaming: false,
+ error: null,
+ },
+ },
+ uiActions: { dismissBtw },
+ },
+ );
+
+ await act(async () => {
+ stdin.write('a');
+ });
+
+ await waitFor(() => {
+ expect(dismissBtw).toHaveBeenCalled();
+ expect(clean(stdout.lastFrameRaw())).toContain('a');
+ });
+ unmount();
+ });
+
+ it('blocks typing when Btw is streaming', async () => {
+ const dismissBtw = vi.fn();
+ const { stdin, stdout, unmount } = await renderWithProviders(
+ ,
+ {
+ uiState: {
+ btwState: {
+ isActive: true,
+ query: '',
+ response: '',
+ isStreaming: true,
+ error: null,
+ },
+ },
+ uiActions: { dismissBtw },
+ },
+ );
+
+ await act(async () => {
+ stdin.write('Z');
+ });
+
+ await waitFor(() => {
+ expect(dismissBtw).not.toHaveBeenCalled();
+ expect(clean(stdout.lastFrameRaw())).not.toContain('Z');
+ });
+ unmount();
+ });
});
});
diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx
index 64dba15561..1ff100d0a3 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -694,6 +694,27 @@ export const InputPrompt: React.FC = ({
dismissBtw();
return true;
}
+
+ const isPrintable =
+ key.sequence &&
+ !key.ctrl &&
+ !key.cmd &&
+ !key.alt &&
+ key.sequence.length === 1;
+
+ const isEditingKey =
+ isPrintable ||
+ key.name === 'backspace' ||
+ key.name === 'delete' ||
+ key.name === 'paste';
+
+ if (isEditingKey) {
+ if (btwState.isStreaming) {
+ return true;
+ } else {
+ dismissBtw();
+ }
+ }
}
// Reset completion suppression if the user performs any action other than
@@ -1386,6 +1407,7 @@ export const InputPrompt: React.FC = ({
isHelpDismissKey,
settings,
btwState.isActive,
+ btwState.isStreaming,
dismissBtw,
],
);