feat(tui): supported risk notes with warning glyphs in settings lists

- Add support for risk notes and warning markers on setting items in the TUI settings list component.
- Update the external thinking setting schema and help command to include a warning about provider abuse enforcement.
This commit is contained in:
can1357
2026-08-19 16:26:57 +02:00
parent 5cbdd740bf
commit 416b30a8d5
9 changed files with 126 additions and 46 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Changed
- `/settings` rows can now carry a risk note: a warning glyph on the row plus a warning-colored line above the description. `External Thinking` (`externalThinking`, `--external-thinking`) is the first user — providers have flagged the request shape it produces as abuse, up to account-level enforcement, so both the settings entry and `--help` now say so.
## [17.3.8] - 2026-08-19
### Added
@@ -78,7 +78,8 @@ export const launchHelp = {
description: "Enable the advisor runtime (passively reviews each turn and injects notes)",
}),
"external-thinking": Flags.boolean({
description: "Use a private scratchpad while disabling supported GPT, Claude, and Gemini reasoning",
description:
"Use a private scratchpad while disabling supported GPT, Claude, and Gemini reasoning (at your own risk: providers have flagged this request shape as abuse)",
}),
hook: Flags.string({ description: "Load a hook/extension file (can be used multiple times)", multiple: true }),
extension: Flags.string({
@@ -199,6 +199,12 @@ interface UiBase {
group?: string;
label: string;
description: string;
/**
* Risk note. Marks the settings row with a warning glyph and renders above
* the description in warning styling. For settings that can get the user
* rate-limited, flagged, or banned — not for merely advanced options.
*/
warning?: string;
/** Condition function name - setting only shown when true */
condition?: string;
}
@@ -1139,6 +1145,8 @@ export const SETTINGS_SCHEMA = {
group: "Thinking",
label: "External Thinking",
description: "Private scratchpad; not shown to user. Disables supported GPT, Claude, and Gemini reasoning",
warning:
"At your own risk: providers have flagged this request shape as abuse, up to account-level enforcement",
},
},
@@ -36,6 +36,8 @@ interface BaseSettingDef {
path: SettingPath;
label: string;
description: string;
/** Risk note shown in warning styling; set for settings that can get the user flagged or banned. */
warning?: string;
tab: SettingTab;
/** Section within the tab; items are ordered by TAB_GROUPS[tab] and rendered under a heading row. */
group?: string;
@@ -162,7 +164,15 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
const schemaType = getType(path);
const condition = ui.condition ? CONDITIONS[ui.condition] : undefined;
const base = { path, label: ui.label, description: ui.description, tab: ui.tab, group: ui.group, condition };
const base = {
path,
label: ui.label,
description: ui.description,
warning: ui.warning,
tab: ui.tab,
group: ui.group,
condition,
};
if (schemaType === "boolean") {
return { ...base, type: "boolean" };
@@ -927,67 +927,47 @@ export class SettingsSelectorComponent implements Component {
}
const currentValue = this.#getCurrentValue(def);
const changed = this.#isChanged(def, currentValue);
const item = {
id: def.path,
label: def.label,
description: def.description,
warning: def.warning,
changed: this.#isChanged(def, currentValue),
};
switch (def.type) {
case "boolean":
return {
id: def.path,
label: def.label,
description: def.description,
currentValue: currentValue ? "true" : "false",
values: ["true", "false"],
changed,
};
return { ...item, currentValue: currentValue ? "true" : "false", values: ["true", "false"] };
case "enum":
return {
id: def.path,
label: def.label,
description: def.description,
currentValue: String(currentValue ?? ""),
values: [...def.values],
changed,
};
return { ...item, currentValue: String(currentValue ?? ""), values: [...def.values] };
case "submenu":
return {
id: def.path,
label: def.label,
description: def.description,
...item,
currentValue: this.#getSubmenuCurrentValue(def.path, currentValue),
submenu: (cv, done) => this.#createSubmenu(def, cv, done),
changed,
};
case "text":
return {
id: def.path,
label: def.label,
description: def.description,
...item,
currentValue: this.#formatTextInputValue(def, currentValue),
submenu: (cv, done) => this.#createTextInput(def, cv, done),
changed,
};
case "providerLimits":
return {
id: def.path,
label: def.label,
description: def.description,
...item,
currentValue: this.#formatProviderLimitsValue(currentValue),
submenu: (_cv, done) => this.#createProviderLimitsInput(done),
changed,
};
case "multiselect":
return {
id: def.path,
label: def.label,
description: def.description,
...item,
currentValue: this.#formatMultiSelectValue(def, currentValue),
submenu: (_cv, done) => this.#createMultiSelect(def, done),
changed,
};
}
}
@@ -254,6 +254,8 @@ export function getSettingsListTheme(): SettingsListTheme {
label: (text: string) => text,
value: (text: string) => text,
description: (text: string) => text,
warning: (text: string) => text,
warningMark: "!",
cursor: "> ",
hint: (text: string) => text,
heading: (text: string) => text,
@@ -267,6 +269,8 @@ export function getSettingsListTheme(): SettingsListTheme {
value: (text: string, selected: boolean, changed: boolean) =>
changed ? theme.fg("statusLineGitDirty", text) : selected ? theme.fg("accent", text) : theme.fg("muted", text),
description: (text: string) => theme.fg("dim", text),
warning: (text: string) => theme.fg("warning", text),
warningMark: theme.status.warning,
cursor: theme.fg("accent", `${theme.nav.cursor} `),
hint: (text: string) => theme.fg("dim", text),
heading: (text: string, dimmed: boolean) =>