Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
211 changes: 211 additions & 0 deletions docs/design/prompt-suggestion/prompt-suggestion-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Prompt Suggestion (NES) Design

> Predicts what the user would naturally type next after the AI completes a response, showing it as ghost text in the input prompt.
>
> Implementation status: `prompt-suggestion-implementation.md`. Speculation engine: `speculation-design.md`.

## Overview

A **prompt suggestion** (Next-step Suggestion / NES) is a short prediction (2-12 words) of the user's next input, generated by an LLM call after each AI response. It appears as ghost text in the input prompt. The user can accept it with Tab/Enter/Right Arrow or dismiss it by typing.

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│ AppContainer (CLI) │
│ │
│ Responding → Idle transition │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Guard Conditions (11 categories) │ │
│ │ settings, interactive, sdk, plan mode, dialogs, │ │
│ │ elicitation, API error │ │
│ └────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ generatePromptSuggestion() │ │
│ │ │ │
│ │ ┌─── CacheSafeParams available? ───┐ │ │
│ │ │ │ │ │
│ │ ▼ YES NO ▼ │ │
│ │ runForkedQuery() BaseLlmClient.generateJson() │ │
│ │ (cache-aware) (standalone fallback) │ │
│ │ │ │
│ │ ──── SUGGESTION_PROMPT ──── │ │
│ │ ──── 12 filter rules ────── │ │
│ │ ──── getFilterReason() ──── │ │
│ └────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ FollowupController (framework-agnostic) │ │
│ │ 300ms delay → show as ghost text │ │
│ │ │ │
│ │ Tab → accept (fill input) │ │
│ │ Enter → accept + submit │ │
│ │ Right → accept (fill input) │ │
│ │ Type → dismiss + abort speculation │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Telemetry (PromptSuggestionEvent) │ │
│ │ outcome, accept_method, timing, similarity, │ │
│ │ keystroke, focus, suppression reason, prompt_id │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```

## Suggestion Generation

### LLM Prompt

```
[SUGGESTION MODE: Suggest what the user might naturally type next.]

Your job is to predict what THEY would type - not what you think they should do.
THE TEST: Would they think "I was just about to type that"?

EXAMPLES:
User asked "fix the bug and run tests", bug is fixed → "run the tests"
After code written → "try it out"
Task complete, obvious follow-up → "commit this" or "push it"

Format: 2-12 words, match the user's style. Or nothing.
Reply with ONLY the suggestion, no quotes or explanation.
```

### Filter Rules (12)

| Rule | Example blocked |
| ------------------ | ------------------------------------------------ |
| done | "done" |
| meta_text | "nothing found", "no suggestion", "silence" |
| meta_wrapped | "(silence)", "[no suggestion]" |
| error_message | "api error: 500" |
| prefixed_label | "Suggestion: commit" |
| too_few_words | "hmm" (but allows "yes", "commit", "push" etc.) |
| too_many_words | > 12 words |
| too_long | >= 100 chars |
| multiple_sentences | "Run tests. Then commit." |
| has_formatting | newlines, markdown bold |
| evaluative | "looks good", "thanks" (with \b word boundaries) |
| ai_voice | "Let me...", "I'll...", "Here's..." |

### Guard Conditions

**AppContainer useEffect (13 checks in code):**

| Guard | Check |
| -------------------- | --------------------------------------------------- |
| Settings toggle | `enableFollowupSuggestions` |
| Non-interactive | `config.isInteractive()` |
| SDK mode | `!config.getSdkMode()` |
| Streaming transition | `Responding → Idle` (2 checks) |
| API error (history) | `historyManager.history[last]?.type !== 'error'` |
| API error (pending) | `!pendingGeminiHistoryItems.some(type === 'error')` |
| Confirmation dialogs | shell + general + loop detection (3 checks) |
| Permission dialog | `isPermissionsDialogOpen` |
| Elicitation | `settingInputRequests.length === 0` |
| Plan mode | `ApprovalMode.PLAN` |

**Inside generatePromptSuggestion():**

| Guard | Check |
| ------------------ | ---------------- |
| Early conversation | `modelTurns < 2` |

**Separate feature flags (not in guard block):**

| Flag | Controls |
| -------------------- | ------------------------------------------------------- |
| `enableCacheSharing` | Whether to use forked query or fallback to generateJson |
| `enableSpeculation` | Whether to start speculation on suggestion display |

## State Management

### FollowupState

```typescript
interface FollowupState {
suggestion: string | null;
isVisible: boolean;
shownAt: number; // timestamp for telemetry
}
```

### FollowupController

Framework-agnostic controller shared by CLI (Ink) and WebUI (React):

- `setSuggestion(text)` — 300ms delayed show, null clears immediately
- `accept(method)` — clears state, fires `onAccept` via microtask, 100ms debounce lock
- `dismiss()` — clears state, logs `ignored` telemetry
- `clear()` — hard reset all state + timers
- `Object.freeze(INITIAL_FOLLOWUP_STATE)` prevents accidental mutation

## Keyboard Interaction

| Key | CLI | WebUI |
| ----------- | --------------------------- | ------------------------------------ |
| Tab | Fill input (no submit) | Fill input (no submit) |
| Enter | Fill + submit | Fill + submit (`explicitText` param) |
| Right Arrow | Fill input (no submit) | Fill input (no submit) |
| Typing | Dismiss + abort speculation | Dismiss |
| Paste | Dismiss + abort speculation | Dismiss |

### Key Binding Note

The Tab handler uses `key.name === 'tab'` explicitly (not `ACCEPT_SUGGESTION` matcher) because `ACCEPT_SUGGESTION` also matches Enter, which must fall through to the SUBMIT handler.

## Telemetry

### PromptSuggestionEvent

| Field | Type | Description |
| -------------------------- | --------------------------- | ----------------------------------- |
| outcome | accepted/ignored/suppressed | Final outcome |
| prompt_id | string | Default: 'user_intent' |
| accept_method | tab/enter/right | How user accepted |
| time_to_accept_ms | number | Time from shown to accept |
| time_to_ignore_ms | number | Time from shown to dismiss |
| time_to_first_keystroke_ms | number | Time to first keystroke while shown |
| suggestion_length | number | Character count |
| similarity | number | 1.0 for accept, 0.0 for ignore |
| was_focused_when_shown | boolean | Terminal had focus |
| reason | string | For suppressed: filter rule name |

### SpeculationEvent

| Field | Type | Description |
| ------------------------ | ----------------------- | ------------------------- |
| outcome | accepted/aborted/failed | Speculation result |
| turns_used | number | API round-trips |
| files_written | number | Files in overlay |
| tool_use_count | number | Tools executed |
| duration_ms | number | Wall-clock time |
| boundary_type | string | What stopped speculation |
| had_pipelined_suggestion | boolean | Next suggestion generated |

## Feature Flags and Settings

| Setting | Type | Default | Description |
| --------------------------- | ------- | ------- | -------------------------------------------------------------------------------- |
| `enableFollowupSuggestions` | boolean | true | Master toggle for prompt suggestions |
| `enableCacheSharing` | boolean | true | Use cache-aware forked queries |
| `enableSpeculation` | boolean | false | Predictive execution engine |
| `fastModel` (top-level) | string | "" | Model for all background tasks (empty = use main model). Set via `/model --fast` |

### Thinking Mode

Thinking/reasoning is explicitly disabled (`thinkingConfig: { includeThoughts: false }`) for all background task paths:

- **Forked query path** (`createForkedChat`) — overrides `thinkingConfig` in the cloned `generationConfig`, covering both suggestion generation and speculation
- **BaseLlm fallback path** (`generateViaBaseLlm`) — per-request config overrides base content generator's thinking settings

This is safe because:

- Cache prefix is determined by systemInstruction + tools + history, not `thinkingConfig` — cache hits are unaffected
- All backends (Gemini, OpenAI-compatible, Anthropic) handle `includeThoughts: false` by omitting the thinking field — no API errors on models without thinking support
- Suggestion generation and speculation don't benefit from reasoning tokens
85 changes: 85 additions & 0 deletions docs/design/prompt-suggestion/prompt-suggestion-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Prompt Suggestion Implementation Status

> Tracks the implementation status of the prompt suggestion (NES) feature across all packages.

## Core Module (`packages/core/src/followup/`)

| Component | Status | Lines | Description |
| ------------------------ | ------- | ----- | ------------------------------------------------------------- |
| `followupState.ts` | ✅ Done | ~230 | Framework-agnostic controller with timer/debounce |
| `suggestionGenerator.ts` | ✅ Done | ~260 | LLM generation + 12 filter rules + forked query support |
| `forkedQuery.ts` | ✅ Done | ~240 | CacheSafeParams + createForkedChat + runForkedQuery |
| `overlayFs.ts` | ✅ Done | ~140 | Copy-on-write overlay filesystem |
| `speculationToolGate.ts` | ✅ Done | ~150 | Tool boundary enforcement with AST shell parser |
| `speculation.ts` | ✅ Done | ~540 | Speculation engine with pipelined suggestion + model override |

## CLI Integration (`packages/cli/`)

| Component | Status | Description |
| ---------------------------- | ------- | ---------------------------------------------------------- |
| `AppContainer.tsx` | ✅ Done | Suggestion generation, speculation lifecycle, UI rendering |
| `InputPrompt.tsx` | ✅ Done | Tab/Enter/Right Arrow acceptance, dismiss + abort |
| `Composer.tsx` | ✅ Done | Props threading |
| `UIStateContext.tsx` | ✅ Done | promptSuggestion + dismissPromptSuggestion |
| `useFollowupSuggestions.tsx` | ✅ Done | React hook with telemetry + keystroke tracking |
| `settingsSchema.ts` | ✅ Done | 3 feature flags + fastModel setting |
| `settings.schema.json` | ✅ Done | VSCode settings schema |

## WebUI Integration (`packages/webui/`)

| Component | Status | Description |
| --------------------------- | ------- | ------------------------------------------- |
| `InputForm.tsx` | ✅ Done | Tab/Enter/Right Arrow + explicitText submit |
| `useFollowupSuggestions.ts` | ✅ Done | React hook with onOutcome support |
| `followup.ts` | ✅ Done | Subpath entry |
| `components.css` | ✅ Done | Ghost text styling |
| `vite.config.followup.ts` | ✅ Done | Separate build config |

## Telemetry (`packages/core/src/telemetry/`)

| Component | Status | Description |
| ----------------------- | ------- | -------------------- |
| `PromptSuggestionEvent` | ✅ Done | 10 fields |
| `SpeculationEvent` | ✅ Done | 7 fields |
| `logPromptSuggestion()` | ✅ Done | OpenTelemetry logger |
| `logSpeculation()` | ✅ Done | OpenTelemetry logger |

## Test Coverage

| Test File | Tests | Description |
| ----------------------------- | ----- | --------------------------------------------------------------- |
| `followupState.test.ts` | 14 | Controller timer, debounce, accept callback, onOutcome, clear |
| `suggestionGenerator.test.ts` | 16 | All 12 filter rules + edge cases + false positives |
| `overlayFs.test.ts` | 15 | COW write, read resolution, apply, cleanup, path traversal |
| `speculationToolGate.test.ts` | 27 | Tool categories, approval mode, shell AST, path rewrite |
| `forkedQuery.test.ts` | 6 | Cache params save/get/clear, deep clone, version detection |
| `speculation.test.ts` | 7 | ensureToolResultPairing edge cases |
| `smoke.test.ts` | 21 | Cross-module E2E: filter + overlay + toolGate + cache + pairing |
| `InputPrompt.test.tsx` | 4 | Tab, Enter+submit, Right Arrow, completion guard |

## Audit History

| Round | Issues Found | Issues Fixed |
| --------------- | ------------ | -------------------------------------------------------- |
| R1-R4 | 10 | 10 (rule engine → LLM, state simplification) |
| R5-R6 | 2 | 2 (Enter keybinding conflict, Right Arrow telemetry) |
| R7-R8 | 3 | 3 (WebUI telemetry, dead type, test coverage) |
| R9 | 0 | — (convergence) |
| R10-R11 | 1 | 1 (historyManager dep) |
| R12-R13 | 1 | 1 (evaluative regex word boundaries) |
| Phase 1+2 R1-R4 | 20+ | 20+ (permission bypass, overlay safety, race conditions) |
| **Total** | **37+** | **37+** |

## Claude Code Alignment

| Feature | Alignment | Notes |
| -------------------------------- | --------- | ------------------------------------- |
| Prompt text | 100% | Identical (brand name only) |
| 12 filter rules | 100%+ | \b word boundaries improvement |
| UI interaction (Tab/Enter/Right) | 100% | |
| Guard conditions | 100% | 13 checks |
| Telemetry | 100% | 10+7 fields |
| Cache sharing | ✅ | DashScope cache_control |
| Speculation | ✅ | COW overlay + tool gating |
| Pipelined suggestion | ✅ | Generated after speculation completes |
| State management | 100%+ | Controller pattern, Object.freeze |
Loading
Loading