feat(coding-agent): added toggleable prompt cache miss markers

- Added `display.cacheMissMarker` setting to enable visual indicators for prompt cache invalidation in assistant messages.
- Included updated theme symbols to represent cache misses across supported icon sets.
- Configured the selector controller to rebuild the chat display when the marker visibility setting is toggled.
This commit is contained in:
can1357
2026-06-19 07:53:01 +02:00
parent ed73429faa
commit a9c493db13
7 changed files with 37 additions and 7 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
## [Unreleased]
### Added
- Added visual markers to assistant messages when prompt caching is invalidated
- Added a prompt-cache miss marker: a slim `⊘ cache miss · <n> tokens` divider above an assistant turn whose request lost the provider prompt cache. Detected from per-turn usage — the previous turn cached a meaningful prefix but this request read none of it back and reprocessed the prompt (e.g. after a thinking-level / service-tier / model change, tool or system-prompt change, or a history rewrite). Reports the reprocessed token count. Toggle with the `display.cacheMissMarker` setting (Appearance → Display, on by default).
### Changed
@@ -849,6 +849,17 @@ export const SETTINGS_SCHEMA = {
},
},
"display.cacheMissMarker": {
type: "boolean",
default: true,
ui: {
tab: "appearance",
group: "Display",
label: "Cache Miss Marker",
description: "Show a divider above an assistant turn whose request lost (missed) the prompt cache",
},
},
showHardwareCursor: {
type: "boolean",
default: true, // will be computed based on platform if undefined
@@ -252,8 +252,10 @@ export class ChatTranscriptBuilder {
);
this.container.addChild(assistantComponent);
const invalidation = detectCacheInvalidation(this.#lastAssistantUsage, message.usage);
if (invalidation) assistantComponent.setCacheInvalidation(invalidation);
if (settings.get("display.cacheMissMarker")) {
const invalidation = detectCacheInvalidation(this.#lastAssistantUsage, message.usage);
if (invalidation) assistantComponent.setCacheInvalidation(invalidation);
}
if (message.usage.cacheRead + message.usage.cacheWrite + message.usage.input > 0) {
this.#lastAssistantUsage = message.usage;
}
@@ -664,8 +664,10 @@ export class EventController {
// meaningful prefix and this request read none of it back, flag the turn.
const usage = event.message.usage;
if (usage.cacheRead + usage.cacheWrite + usage.input > 0) {
const invalidation = detectCacheInvalidation(this.ctx.lastAssistantUsage, usage);
if (invalidation) this.ctx.streamingComponent.setCacheInvalidation(invalidation);
if (settings.get("display.cacheMissMarker")) {
const invalidation = detectCacheInvalidation(this.ctx.lastAssistantUsage, usage);
if (invalidation) this.ctx.streamingComponent.setCacheInvalidation(invalidation);
}
this.ctx.lastAssistantUsage = usage;
}
this.#lastAssistantComponent = this.ctx.streamingComponent;
@@ -327,6 +327,13 @@ export class SelectorController {
// InputController.toggleThinkingBlockVisibility).
this.ctx.ui.resetDisplay();
break;
case "display.cacheMissMarker":
// Rebuild re-runs the usage-based detection under the new setting so
// markers appear/disappear; full reset retires any already committed
// to native scrollback (mirrors hideThinking).
this.ctx.rebuildChatFromMessages();
this.ctx.ui.resetDisplay();
break;
case "tui.tight":
setTuiTight(value as boolean);
this.ctx.ui.invalidate();
@@ -111,6 +111,7 @@ export type SymbolKey =
| "icon.agents"
| "icon.job"
| "icon.cache"
| "icon.cacheMiss"
| "icon.input"
| "icon.output"
| "icon.host"
@@ -310,6 +311,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
"icon.agents": "👥",
"icon.job": "⚙",
"icon.cache": "💾",
"icon.cacheMiss": "⊘",
"icon.input": "⤵",
"icon.output": "⤴",
"icon.host": "🖥",
@@ -579,6 +581,8 @@ const NERD_SYMBOLS: SymbolMap = {
"icon.job": "\uf013",
// pick:  | alt:  
"icon.cache": "\uf1c0",
// pick: (fa-ban) | alt: ⊘
"icon.cacheMiss": "\uf05e",
// pick:  | alt:  →
"icon.input": "\uf090",
// pick:  | alt:  →
@@ -810,6 +814,7 @@ const ASCII_SYMBOLS: SymbolMap = {
"icon.agents": "AG",
"icon.job": "bg",
"icon.cache": "cache",
"icon.cacheMiss": "!",
"icon.input": "in:",
"icon.output": "out:",
"icon.host": "host",
@@ -1770,6 +1775,7 @@ export class Theme {
agents: this.#symbols["icon.agents"],
job: this.#symbols["icon.job"],
cache: this.#symbols["icon.cache"],
cacheMiss: this.#symbols["icon.cacheMiss"],
input: this.#symbols["icon.input"],
output: this.#symbols["icon.output"],
host: this.#symbols["icon.host"],
@@ -412,8 +412,10 @@ export class UiHelpers {
const assistantComponent = lastChild instanceof AssistantMessageComponent ? lastChild : undefined;
if (assistantComponent) {
const usage = message.usage;
const invalidation = detectCacheInvalidation(this.ctx.lastAssistantUsage, usage);
if (invalidation) assistantComponent.setCacheInvalidation(invalidation);
if (this.ctx.settings.get("display.cacheMissMarker")) {
const invalidation = detectCacheInvalidation(this.ctx.lastAssistantUsage, usage);
if (invalidation) assistantComponent.setCacheInvalidation(invalidation);
}
if (usage.cacheRead + usage.cacheWrite + usage.input > 0) {
this.ctx.lastAssistantUsage = usage;
}