Repository navigation
feat(cli): Add model toggle hotkey (Ctrl+F) #6486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
ac2945a
feat(cli): Add model toggle hotkey (Ctrl+F)
f7179d7
fix(cli): resolve target auth type for cross-provider model toggle
8bf4189
refactor(cli): extract toggle decision logic into testable pure funct…
23cf916
fix(cli): narrow agent PTY toggle guard + add PTY tracking tests
63d423a
test(cli): add Ctrl+F delegation tests for AgentChatContent
934ca93
fix(cli): suppress Ctrl+F cursor-right in non-vim mode + fix test name
4b59e75
fix(cli): add handleToggleKeypress to InputPrompt test mocks
e85e8f9
test(cli): cover Ctrl+F suppression branch + invalidation scenario
0e43f0f
fix(cli): isolate switchModel rejection from post-switch side effects
956e9bf
test(cli): extract setAgentShellFocused mock for future assertions
f2ddbb1
fix(cli): localize model toggle runtime messages + gate debug log
0a45b20
fix(cli): document cross-provider toggleModel disambiguation + add am…
21e82a4
fix(cli): resolve merge conflicts with upstream/main in InputPrompt t…
50e819a
fix(cli): restore deferUntilIdle for Ctrl+Q queue + remove provenance…
c5eeee8
fix(cli): resolve merge conflicts with main in InputPrompt submit + p…
qwen-code-dev-bot ec47b37
fix(cli): add handleToggleKeypress to test mocks in InputPrompt.test.tsx
69ae108
Merge upstream/main into feat/model-toggle-hotkey
ef729cc
fix(cli): address review feedback on model toggle hotkey
a7701b1
fix(cli): block model toggle only for the active agent's PTY
f74c6b7
test(cli): address review feedback on test files
e238a00
fix(cli): address CI review feedback on model toggle hotkey
2331c9b
test(cli): add mutant survivor tests for CI review suggestions
ecd7488
fix(cli): address CI review feedback on model toggle hotkey
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
integration-tests/interactive/model-toggle-hotkey.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /** | ||
| * @license | ||
| * Copyright 2026 Qwen Team | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| /** | ||
| * E2E test for the Ctrl+F model toggle hotkey. | ||
| * | ||
| * Sends a raw Ctrl+F byte (0x06) through the PTY — the same byte | ||
| * a non-kitty PTY would send for Ctrl+F — and asserts the "Switched | ||
| * to" message appears in the TUI output. | ||
| * | ||
| * NOTE: PTY-based interactive tests don't work on Windows (node-pty | ||
| * limitation). Skipped via IS_WINDOWS guard. | ||
| */ | ||
|
|
||
| import { describe, it, expect, beforeEach, afterEach } from 'vitest'; | ||
| import { TestRig } from '../test-helper.js'; | ||
|
|
||
| const IS_WINDOWS = process.platform === 'win32'; | ||
|
|
||
| (IS_WINDOWS ? describe.skip : describe)('model toggle hotkey', () => { | ||
| let rig: TestRig; | ||
| let savedLang: string | undefined; | ||
|
|
||
| beforeEach(async () => { | ||
| // Save original value so afterEach can restore it. | ||
| savedLang = process.env['QWEN_CODE_LANG']; | ||
| // Pre-seed locale so waitForText('Type your message') is stable | ||
| // regardless of the host's locale settings. | ||
| process.env['QWEN_CODE_LANG'] = 'en'; | ||
|
|
||
| rig = new TestRig(); | ||
| await rig.setup('model-toggle-hotkey-e2e', { | ||
| settings: { | ||
| security: { | ||
| auth: { | ||
| selectedType: 'openai', | ||
| }, | ||
| }, | ||
| model: { | ||
| name: 'model-a', | ||
| toggleModel: 'model-b', | ||
| }, | ||
| modelProviders: { | ||
| openai: [{ id: 'model-a' }, { id: 'model-b' }], | ||
| }, | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| // Restore original value instead of blindly deleting. | ||
| if (savedLang !== undefined) { | ||
| process.env['QWEN_CODE_LANG'] = savedLang; | ||
| } else { | ||
| delete process.env['QWEN_CODE_LANG']; | ||
| } | ||
| await rig.cleanup(); | ||
| }); | ||
|
|
||
| it('should toggle model on Ctrl+F and show Switched to message', async () => { | ||
| const { ptyProcess, promise } = rig.runInteractive(); | ||
|
|
||
| let output = ''; | ||
| ptyProcess.onData((data) => { | ||
| output += data; | ||
| }); | ||
|
|
||
| // If the CLI exits early (crash, OOM), surface the exit code in the | ||
| // failure message instead of a generic waitForText timeout. | ||
| let earlyExit: string | null = null; | ||
| void promise.then(({ exitCode, output: exitOutput }) => { | ||
| earlyExit = `CLI exited with code ${exitCode}. Last output:\n${exitOutput.slice(-500)}`; | ||
| }); | ||
|
|
||
| // Wait for CLI to be ready | ||
| const isReady = await rig.waitForText('Type your message', 30000); | ||
| expect(isReady, earlyExit ?? 'CLI did not start in interactive mode').toBe( | ||
| true, | ||
| ); | ||
|
|
||
| // Raw Ctrl+F byte (0x06). In a PTY without kitty-protocol | ||
| // negotiation this is what the terminal sends for Ctrl+F; | ||
| // Ink decodes it as Key({ctrl: true, name: 'f'}). | ||
| const CTRL_F = '\x06'; | ||
|
|
||
| // Toggle to toggleModel (model-b) and wait for the info message | ||
| ptyProcess.write(CTRL_F); | ||
| const switchedToModelB = await rig.waitForText( | ||
| 'Switched to model-b', | ||
| 10000, | ||
| ); | ||
| expect( | ||
| switchedToModelB, | ||
| earlyExit ?? | ||
| `Expected 'Switched to model-b' after first toggle. Output:\n${output}`, | ||
| ).toBe(true); | ||
|
|
||
| // Toggle back to original model (model-a) | ||
| ptyProcess.write(CTRL_F); | ||
| const switchedToModelA = await rig.waitForText( | ||
| 'Switched to model-a', | ||
| 10000, | ||
| ); | ||
| expect( | ||
| switchedToModelA, | ||
| earlyExit ?? | ||
| `Expected 'Switched to model-a' after second toggle. Output:\n${output}`, | ||
| ).toBe(true); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] E2E test never terminates the PTY process
runInteractive()returns{ ptyProcess, promise }but onlyptyProcessis destructured. The CLI process is never stopped —rig.cleanup()only deletes the test directory. Orphaned PTY processes accumulate during test runs.Then at the end of the test, gracefully exit:
— qwen3.7-max via Qwen Code /review