fix(tui): make prompt suggestion configurable (opt-in, default off)

- Add prompt_suggestion: Option<bool> config field with
  prompt_suggestion_enabled() accessor (defaults to false)
- Guard suggestion generation behind the config check
- Use config.default_model() (provider-aware) instead of hardcoded
  deepseek-v4-flash to avoid cross-provider data egress
- Document in config.example.toml

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Punkcan Yang
2026-06-06 17:57:20 +08:00
parent eb1d08b05e
commit e687b07bc6
3 changed files with 13 additions and 4 deletions
+1
View File
@@ -144,6 +144,7 @@ memory_path = "~/.codewhale/memory.md"
allow_shell = true
approval_policy = "on-request" # on-request | untrusted | never
sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access | external-sandbox
# prompt_suggestion = true # opt-in: show ghost-text follow-up question in composer after each turn
# Typed permission rules live in a sibling `permissions.toml` file, not in
# config.toml. This schema slice is ask-only and is parsed for follow-up
+9
View File
@@ -1550,6 +1550,9 @@ pub struct Config {
/// missing optional file doesn't fail the launch.
pub instructions: Option<Vec<String>>,
pub allow_shell: Option<bool>,
/// Opt-in ghost-text follow-up prompt suggestion after each completed turn.
/// Default: false — the user must explicitly set this to true to enable.
pub prompt_suggestion: Option<bool>,
pub approval_policy: Option<String>,
pub sandbox_mode: Option<String>,
pub yolo: Option<bool>,
@@ -2707,6 +2710,11 @@ impl Config {
self.allow_shell.unwrap_or(false)
}
/// Whether ghost-text prompt suggestion is enabled (opt-in, default off).
pub fn prompt_suggestion_enabled(&self) -> bool {
self.prompt_suggestion.unwrap_or(false)
}
/// Return the maximum number of concurrent sub-agents.
/// Checks `[subagents] max_concurrent` first, then top-level `max_subagents`,
/// then falls back to `DEFAULT_MAX_SUBAGENTS`.
@@ -4253,6 +4261,7 @@ fn merge_config(base: Config, override_cfg: Config) -> Config {
// both — they list `~/global.md` inside the project array.
instructions: override_cfg.instructions.or(base.instructions),
allow_shell: override_cfg.allow_shell.or(base.allow_shell),
prompt_suggestion: override_cfg.prompt_suggestion.or(base.prompt_suggestion),
yolo: override_cfg.yolo.or(base.yolo),
approval_policy: override_cfg.approval_policy.or(base.approval_policy),
sandbox_mode: override_cfg.sandbox_mode.or(base.sandbox_mode),
+3 -4
View File
@@ -1843,11 +1843,13 @@ async fn run_event_loop(
// Generate ghost-text follow-up suggestion asynchronously.
if status == crate::core::events::TurnOutcomeStatus::Completed
&& config.prompt_suggestion_enabled()
&& app.api_messages.len() >= 2
{
let suggestion_cell = app.prompt_suggestion_cell.clone();
let api_key = config.deepseek_api_key().unwrap_or_default();
let base_url = config.deepseek_base_url();
let model = config.default_model();
let messages: Vec<crate::models::Message> = app.api_messages.clone();
let gen_token = app
.prompt_suggestion_gen
@@ -1860,10 +1862,7 @@ async fn run_event_loop(
);
if let Some(suggestion) =
crate::tui::prompt_suggestion::generate_suggestion(
&api_key,
&base_url,
"deepseek-v4-flash",
&summary,
&api_key, &base_url, &model, &summary,
)
.await
&& let Ok(mut guard) = suggestion_cell.lock()