Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
3df717e
feat(cli, webui): add follow-up suggestions feature
wenshao Mar 20, 2026
27756d7
fix: resolve merge conflicts and build errors
wenshao Mar 20, 2026
34d85a5
fix: remove @agentclientprotocol/sdk from core package.json
wenshao Mar 20, 2026
bec72e0
fix: restore alphabetical order of dependencies in core/package.json
wenshao Mar 20, 2026
b1e29a1
fix: restore package-lock.json from upstream to fix Node 24.x CI
wenshao Mar 20, 2026
004baae
fix: resolve acpConnection test failure and ESLint warning
wenshao Mar 20, 2026
45a5141
Merge upstream/main into feat/followup-suggestions
wenshao Mar 20, 2026
bc6e62f
style: apply prettier formatting after merge
wenshao Mar 20, 2026
113ae98
Merge main into feat/followup-suggestions
wenshao Mar 23, 2026
0ec27e5
fix(followup): address review issues in follow-up suggestions
wenshao Mar 24, 2026
46e368e
Merge remote-tracking branch 'origin/main' into feat/followup-suggest…
wenshao Mar 25, 2026
826769e
fix(followup): address review feedback from PR #2525
wenshao Mar 25, 2026
9e487cf
fix(followup): address second round of review feedback
wenshao Mar 25, 2026
d8ba294
fix(followup): clear suggestions on new turn, add search guards
wenshao Mar 25, 2026
61a79b2
fix(followup): address third round of review feedback
wenshao Mar 25, 2026
26a14eb
fix(followup): fix arrow cycling guard and align rule conditions with…
wenshao Mar 25, 2026
a819492
fix(followup): reset acceptingRef in clear() to prevent deadlock
wenshao Mar 25, 2026
61e64c9
fix(followup): cancel pending timeout in dismiss() and accept()
wenshao Mar 25, 2026
626747f
fix(followup): reset lastIndex in removeRules() for g/y flag safety
wenshao Mar 25, 2026
de85a12
fix(vscode-ide-companion): mark @qwen-code/qwen-code-core as external…
yiliang114 Mar 25, 2026
1efe382
fix: restore node_modules/@google/gemini-cli-test-utils workspace lin…
yiliang114 Mar 25, 2026
c6e4e4b
Merge remote-tracking branch 'origin/main' into feat/followup-suggest…
wenshao Mar 27, 2026
3b18068
Merge remote-tracking branch 'wenshao/feat/followup-suggestions' into…
wenshao Mar 27, 2026
7c55881
refactor(followup): extract controller and improve rule matching
yiliang114 Mar 27, 2026
d0f38a5
refactor(webui): import followup state from core package
yiliang114 Mar 27, 2026
43b7a23
refactor(followup): simplify generator, revert unrelated changes
yiliang114 Mar 27, 2026
2538bac
fix(followup): fix accept() deadlock and restore UMD globals mapping
yiliang114 Mar 29, 2026
17a4e86
fix(followup): log accept callback errors instead of swallowing them
yiliang114 Mar 29, 2026
c06879b
refactor(webui): move followup hook to separate subpath entry
yiliang114 Mar 29, 2026
08c77a0
fix(webui): restore UMD build and isolate core from root type boundary
yiliang114 Mar 29, 2026
eb1fa17
refactor(followup): replace rule-based suggestions with LLM-based pro…
wenshao Apr 1, 2026
5ae2f3b
fix(followup): address qwen3.6-plus-preview review findings
wenshao Apr 1, 2026
e43607f
fix(settings): update suggestion feature description to match current…
wenshao Apr 1, 2026
d947326
fix(followup): fix WebUI Enter submitting empty text + defend onOutcome
wenshao Apr 1, 2026
edab4b2
fix(followup): allow setSuggestion(null) when disabled + fix dts clobber
wenshao Apr 1, 2026
ad12907
fix(webui): thread explicitText through submit chain for Enter accept
wenshao Apr 1, 2026
f7dcb1c
fix(followup): address Copilot review — 4 fixes
wenshao Apr 1, 2026
7ef0246
fix(followup): address Copilot review — curated history, type compat,…
wenshao Apr 1, 2026
eae5a8c
feat(followup): add prompt cache sharing + speculation engine
wenshao Apr 1, 2026
3dc19f1
fix(followup): address Copilot review — curated history, type compat,…
wenshao Apr 1, 2026
36c5f57
fix(speculation): use index-based tracking for boundary tool pairing
wenshao Apr 1, 2026
32668b0
fix(speculation): handle undefined functionCall.name + wrap rewritePa…
wenshao Apr 1, 2026
851fdf2
feat(followup): pipelined suggestion, UI rendering, dismiss abort
wenshao Apr 1, 2026
4b59423
fix(followup): clear cache on reset, truncate history, fix test + com…
wenshao Apr 1, 2026
c24b011
Merge remote-tracking branch 'origin/main' into feat/followup-suggest…
wenshao Apr 1, 2026
8b1372c
docs(design): add prompt suggestion design documentation
wenshao Apr 1, 2026
45e2e2f
fix(overlay): align catch comment with silent behavior
wenshao Apr 1, 2026
2b148ae
fix(followup): wire augmented context into pipelined suggestion + gua…
wenshao Apr 1, 2026
d336f39
fix(speculation): filter thought parts + add filePath to path keys
wenshao Apr 1, 2026
8bcce96
fix(overlay): resolve relative paths against realCwd not process.cwd
wenshao Apr 1, 2026
2be9db2
docs(design): fix 4 doc-code inconsistencies
wenshao Apr 1, 2026
706d51c
fix(followup): turns_used metric + reuse SUGGESTION_PROMPT + reduce c…
wenshao Apr 1, 2026
eacfe92
feat(followup): speculation UI tool rendering + speculationModel setting
wenshao Apr 1, 2026
5c725ba
docs(design): sync docs with latest code changes
wenshao Apr 1, 2026
62e487b
test(followup): add unit tests for overlayFs, toolGate, forkedQuery
wenshao Apr 1, 2026
ff04919
test(followup): P0-P2 test coverage for speculation + controller + to…
wenshao Apr 1, 2026
7586650
test(followup): smoke tests + P0-P2 coverage gaps
wenshao Apr 1, 2026
07a82a4
fix(followup): dismiss aborts suggestion, boundary skip inject, paren…
wenshao Apr 1, 2026
f43c913
docs(design): fix speculation accept diagram — boundary skips inject
wenshao Apr 1, 2026
5ce8987
feat(followup): enable cache sharing by default
wenshao Apr 2, 2026
a9e10aa
fix(followup): aborted parent skips loop, acceptSpeculation try/final…
wenshao Apr 2, 2026
64da638
fix(followup): remove debug logs, add function calling fallback for n…
wenshao Apr 2, 2026
6e76f4d
feat(followup): add suggestionModel setting for faster suggestion gen…
wenshao Apr 2, 2026
40061d7
feat(followup): suggestionModel setting, /stats tracking, /about display
wenshao Apr 2, 2026
f695b03
i18n: add Suggestion Model translations for /about display
wenshao Apr 2, 2026
14d905c
fix(followup): always use generateContent for suggestion (not generat…
wenshao Apr 2, 2026
80b5b9f
fix(followup): fix /stats tracking — use ApiResponseEvent constructor
wenshao Apr 2, 2026
2cb115a
i18n: fix Chinese translation for Suggestion Model
wenshao Apr 2, 2026
49702ce
refactor(followup): merge suggestionModel + speculationModel into fas…
wenshao Apr 2, 2026
e9bc686
refactor(settings): move fastModel to top-level (parallel to model)
wenshao Apr 2, 2026
7d3d65d
fix(followup): report usage in both forkedQuery and baseLlm paths
wenshao Apr 2, 2026
fea1739
feat(cli): add /model --fast command to set fast model
wenshao Apr 2, 2026
f0d0e1c
docs(design): update to fastModel (replace suggestionModel/speculatio…
wenshao Apr 2, 2026
c062767
feat(cli): /model --fast opens model selection dialog for fast model
wenshao Apr 2, 2026
c1bc0c6
fix(followup): pass resolved model (not undefined) to runForkedQuery
wenshao Apr 2, 2026
2348093
fix(cli): /model --fast defaults to current fast model in dialog
wenshao Apr 2, 2026
5f01a1d
feat(cli): add --fast tab completion for /model command
wenshao Apr 2, 2026
cb5868e
fix(schema): regenerate settings.schema.json with new followup settings
wenshao Apr 2, 2026
bbad5a9
fix(test): update tests for new Fast Model field in system info
wenshao Apr 2, 2026
9767eaa
ci: trigger PR synchronize event
wenshao Apr 2, 2026
1f60669
fix: address Copilot review comments (batch 4)
wenshao Apr 2, 2026
7bc4aa6
docs(users): add followup suggestions user manual
wenshao Apr 2, 2026
50ed4b4
docs(users): audit fixes for followup suggestions documentation
wenshao Apr 2, 2026
745df21
fix: address Copilot review comments (batch 5)
wenshao Apr 2, 2026
67af606
fix(cli): remove duplicate message in /model --fast command
wenshao Apr 2, 2026
932ec84
fix(i18n): remove unused 'Fast model updated.' translations
wenshao Apr 2, 2026
a3a3623
fix(followup): disable thinking mode for suggestion and speculation
wenshao Apr 2, 2026
7c81c46
docs: document thinking mode auto-disable for background tasks
wenshao Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,16 @@ const SETTINGS_SCHEMA = {
'Show optional feedback dialog after conversations to help improve Qwen performance.',
showInDialog: true,
},
enableFollowupSuggestions: {
type: 'boolean',
label: 'Enable Follow-up Suggestions',
category: 'UI',
requiresRestart: false,
default: true,
description:
'Show context-aware follow-up suggestions after task completion (e.g., "commit this", "run tests"). Press Tab to accept, arrow keys to cycle.',
Comment thread
wenshao marked this conversation as resolved.
Outdated
showInDialog: true,
Comment thread
wenshao marked this conversation as resolved.
},
accessibility: {
type: 'object',
label: 'Accessibility',
Expand Down
56 changes: 56 additions & 0 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
Storage,
SessionEndReason,
SessionStartSource,
generateFollowupSuggestions,
type FollowupSuggestion,
type PermissionMode,
} from '@qwen-code/qwen-code-core';
import { buildResumedHistoryItems } from './utils/resumeHistoryUtils.js';
Expand Down Expand Up @@ -85,6 +87,7 @@ import { useIdeTrustListener } from './hooks/useIdeTrustListener.js';
import { type IdeIntegrationNudgeResult } from './IdeIntegrationNudge.js';
import { type CommandMigrationNudgeResult } from './CommandFormatMigrationNudge.js';
import { useCommandMigration } from './hooks/useCommandMigration.js';
import { extractFollowupSuggestionContext } from './followupHistory.js';
import { migrateTomlCommands } from '../services/command-migration-tool.js';
import { type UpdateObject } from './utils/updateCheck.js';
import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
Expand Down Expand Up @@ -735,6 +738,12 @@ export const AppContainer = (props: AppContainerProps) => {

const agentViewState = useAgentViewState();

// Follow-up suggestions state
const [followupSuggestions, setFollowupSuggestions] = useState<
FollowupSuggestion[]
>([]);
const prevStreamingStateRef = useRef<StreamingState>(StreamingState.Idle);

// Auto-accept indicator — disabled on agent tabs (agents handle their own)
const showAutoAcceptIndicator = useAutoAcceptIndicator({
config,
Expand Down Expand Up @@ -935,6 +944,49 @@ export const AppContainer = (props: AppContainerProps) => {
geminiClient,
]);

// Generate follow-up suggestions when streaming completes
const followupSuggestionsEnabled =
settings.merged.ui?.enableFollowupSuggestions !== false;

useEffect(() => {
// Clear suggestions when a new turn starts (Idle → Responding)
if (
prevStreamingStateRef.current === StreamingState.Idle &&
streamingState === StreamingState.Responding
) {
setFollowupSuggestions([]);
}

// Skip suggestion generation if feature is disabled
if (!followupSuggestionsEnabled) {
prevStreamingStateRef.current = streamingState;
return;
}

// Only trigger when transitioning from Responding to Idle
if (
prevStreamingStateRef.current === StreamingState.Responding &&
streamingState === StreamingState.Idle
) {
const history = historyManager.history;
const context = extractFollowupSuggestionContext(history);

if (context) {
const result = generateFollowupSuggestions(context);
if (result.shouldShow && result.suggestions.length > 0) {
setFollowupSuggestions(result.suggestions);
} else {
setFollowupSuggestions([]);
}
} else {
setFollowupSuggestions([]);
}
}

prevStreamingStateRef.current = streamingState;
// eslint-disable-next-line react-hooks/exhaustive-deps -- only run on streamingState transitions
}, [streamingState, followupSuggestionsEnabled]);

const [idePromptAnswered, setIdePromptAnswered] = useState(false);
const [currentIDE, setCurrentIDE] = useState<IdeInfo | null>(null);

Expand Down Expand Up @@ -1574,6 +1626,8 @@ export const AppContainer = (props: AppContainerProps) => {
isFeedbackDialogOpen,
// Per-task token tracking
taskStartTokens,
// Follow-up suggestions
followupSuggestions,
}),
[
isThemeDialogOpen,
Expand Down Expand Up @@ -1677,6 +1731,8 @@ export const AppContainer = (props: AppContainerProps) => {
isFeedbackDialogOpen,
// Per-task token tracking
taskStartTokens,
// Follow-up suggestions
followupSuggestions,
],
);

Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/ui/components/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const Composer = () => {
? ' ' + t("Press 'i' for INSERT mode and 'Esc' for NORMAL mode.")
: ' ' + t('Type your message or @path/to/file')
}
followupSuggestions={uiState.followupSuggestions}
/>
)}

Expand Down
86 changes: 86 additions & 0 deletions packages/cli/src/ui/components/InputPrompt.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,92 @@ describe('InputPrompt', () => {

const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));

describe('follow-up suggestions', () => {
it('accepts the visible follow-up suggestion on tab when the buffer is empty', async () => {
const { stdin, unmount } = renderWithProviders(
<InputPrompt
{...props}
followupSuggestions={[{ text: 'commit this', priority: 100 }]}
/>,
);
await wait(350);

stdin.write('\t');
await wait();

expect(mockBuffer.insert).toHaveBeenCalledWith('commit this');
unmount();
});

it('does not accept a follow-up suggestion while command completion is active', async () => {
mockCommandCompletion.showSuggestions = true;
mockCommandCompletion.suggestions = [
{
value: '/clear',
label: '/clear',
description: 'Clear screen',
},
] as UseCommandCompletionReturn['suggestions'];

const { stdin, unmount } = renderWithProviders(
<InputPrompt
{...props}
followupSuggestions={[{ text: 'commit this', priority: 100 }]}
/>,
);
await wait(350);

stdin.write('\t');
await wait();

expect(mockBuffer.insert).not.toHaveBeenCalledWith('commit this');
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalled();
unmount();
});

it('cycles to the next follow-up suggestion with the right arrow key', async () => {
const { stdin, unmount } = renderWithProviders(
<InputPrompt
{...props}
followupSuggestions={[
{ text: 'commit this', priority: 100 },
{ text: 'review changes', priority: 90 },
]}
/>,
);
await wait(350);

stdin.write('\u001B[C');
await wait();
stdin.write('\t');
await wait();

expect(mockBuffer.insert).toHaveBeenCalledWith('review changes');
unmount();
});

it('cycles to the previous follow-up suggestion with the left arrow key', async () => {
const { stdin, unmount } = renderWithProviders(
<InputPrompt
{...props}
followupSuggestions={[
{ text: 'commit this', priority: 100 },
{ text: 'review changes', priority: 90 },
]}
/>,
);
await wait(350);

stdin.write('\u001B[D');
await wait();
stdin.write('\t');
await wait();

expect(mockBuffer.insert).toHaveBeenCalledWith('review changes');
unmount();
});
});

it('should call shellHistory.getPreviousCommand on up arrow in shell mode', async () => {
props.shellModeActive = true;
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
Expand Down
Loading
Loading