Skip to content

Commit cf0c01b

Browse files
committed
feat(tui): show execute threshold in sidebar + status header
Sidebar header now displays the per-model execute-threshold alongside the current usage percentage, in a left/right split layout: 47.5% / 65% 475K / 1.0M The left half tells the user how close they are to the next compaction trigger; the right half stays the absolute "tokens used / context window" reading. Status dialog header gets the same shape for visual consistency (was previously "Context 47.5% percent dot 475K / 1.0M tokens" on one right-aligned line). Implementation: - `executeThreshold: number` added to `SidebarSnapshot` so the sidebar can render it without a second RPC round-trip - `buildSidebarSnapshot` now accepts an optional `config` arg and resolves the threshold per-model via the same `resolveExecuteThresholdDetail` flow used by `buildStatusDetail` - Both RPC handler call sites forward the raw config so the sidebar and dialog agree on the resolved value - Sticky cache and client EMPTY_SNAPSHOT updated for the new field; the cache merger inherits `executeThreshold` from the fresh snapshot via `...fresh` so model switches surface immediately Tests: 1479 pass / 0 fail. Lint, typecheck, build clean.
1 parent bb010cd commit cf0c01b

6 files changed

Lines changed: 70 additions & 5 deletions

File tree

packages/plugin/src/plugin/rpc-handlers.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ export function buildSidebarSnapshot(
8383
directory: string,
8484
liveSessionState?: LiveSessionState,
8585
injectionBudgetTokens?: number,
86+
// Optional config so the sidebar can show the effective execute threshold
87+
// alongside `usagePercentage` (e.g. "47.5% / 65%"). Resolved per-model from
88+
// `liveSessionState.liveModelBySession`. When omitted (e.g. legacy test
89+
// callers), the snapshot falls back to the runtime default of 65%.
90+
config?: Record<string, unknown>,
8691
): SidebarSnapshot {
8792
const empty: SidebarSnapshot = {
8893
sessionId,
@@ -108,6 +113,7 @@ export function buildSidebarSnapshot(
108113
conversationTokens: 0,
109114
toolCallTokens: 0,
110115
toolDefinitionTokens: 0,
116+
executeThreshold: 65,
111117
};
112118

113119
try {
@@ -340,6 +346,33 @@ export function buildSidebarSnapshot(
340346
? resolveContextLimit(activeProviderID, activeModelID, { db, sessionID: sessionId })
341347
: 0;
342348

349+
// Resolve the effective execute-threshold percentage for this
350+
// session's active model so the sidebar header can show
351+
// "47.5% / 65%" alongside the absolute "475K / 1.0M". Falls back
352+
// to 65% (the runtime default) when no live model is known yet
353+
// or when no config was passed in. Mirrors the resolution flow
354+
// used by `buildStatusDetail` so the dialog and sidebar agree.
355+
let executeThreshold = 65;
356+
if (config) {
357+
const modelKey =
358+
activeProviderID && activeModelID
359+
? `${activeProviderID}/${activeModelID}`
360+
: undefined;
361+
const pctCfg = config.execute_threshold_percentage as
362+
| number
363+
| { default: number; [k: string]: number }
364+
| undefined;
365+
const tokensCfg = config.execute_threshold_tokens as
366+
| { default?: number; [k: string]: number | undefined }
367+
| undefined;
368+
const thresholdDetail = resolveExecuteThresholdDetail(pctCfg ?? 65, modelKey, 65, {
369+
tokensConfig: tokensCfg,
370+
contextLimit: contextLimit || undefined,
371+
sessionId,
372+
});
373+
executeThreshold = thresholdDetail.percentage;
374+
}
375+
343376
const calibration = resolveModelCalibration(activeProviderID, activeModelID);
344377
const calibrated = calibrateBuckets({
345378
inputTokens,
@@ -377,6 +410,7 @@ export function buildSidebarSnapshot(
377410
conversationTokens: calibrated.conversationTokens,
378411
toolCallTokens: calibrated.toolCallTokens,
379412
toolDefinitionTokens: calibrated.toolDefinitionTokens,
413+
executeThreshold,
380414
};
381415
// Defensive sticky cache: if `inputTokens` briefly drops to 0 mid-turn
382416
// (intermittent — possibly streaming events with empty token shape, or
@@ -404,6 +438,7 @@ export function buildStatusDetail(
404438
directory,
405439
liveSessionState,
406440
injectionBudgetTokens,
441+
config,
407442
);
408443
const detail: StatusDetail = {
409444
...base,
@@ -602,6 +637,7 @@ export function registerRpcHandlers(
602637
dir,
603638
liveSessionState,
604639
injectionBudgetTokens,
640+
rawConfig,
605641
) as unknown as Record<string, unknown>;
606642
});
607643

packages/plugin/src/plugin/sidebar-snapshot-cache.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function makeSnapshot(overrides: Partial<SidebarSnapshot> = {}): SidebarSnapshot
3535
conversationTokens: 0,
3636
toolCallTokens: 0,
3737
toolDefinitionTokens: 0,
38+
executeThreshold: 65,
3839
...overrides,
3940
};
4041
}

packages/plugin/src/shared/rpc-types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ export interface SidebarSnapshot {
4545
* shows this as "Tool Definitions".
4646
*/
4747
toolDefinitionTokens: number;
48+
/**
49+
* Effective execute-threshold percentage for this session's active model,
50+
* after per-model resolution and the tokens→percentage conversion (when
51+
* `execute_threshold_tokens` applies). Surfaces in the sidebar / status
52+
* dialog header alongside `usagePercentage` so users can see how close
53+
* the session is to triggering compaction. Defaults to `65` when no live
54+
* model is known yet — matches the runtime fallback used by the
55+
* scheduler and transform paths.
56+
*/
57+
executeThreshold: number;
4858
}
4959

5060
export interface StatusDetail extends SidebarSnapshot {

packages/plugin/src/tui/data/context-db.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const EMPTY_SNAPSHOT: SidebarSnapshot = {
5858
conversationTokens: 0,
5959
toolCallTokens: 0,
6060
toolDefinitionTokens: 0,
61+
executeThreshold: 65,
6162
};
6263

6364
/**

packages/plugin/src/tui/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,16 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
290290
<text fg={t().textMuted}>v{packageJson.version}</text>
291291
</box>
292292

293-
{/* Context summary line */}
293+
{/* Context summary line. Mirrors the sidebar header layout
294+
("47.5% / 65% 475K / 1.0M") so users can recognize the
295+
same shape in the status dialog. The execute threshold tells
296+
them how close they are to compaction triggering. */}
294297
<box flexDirection="row" justifyContent="space-between" width="100%">
295-
<text fg={t().text}>Context</text>
296298
<text fg={s().usagePercentage >= 80 ? t().error : s().usagePercentage >= 65 ? t().warning : t().accent}>
297-
<b>{s().usagePercentage.toFixed(1)}%</b> · {fmt(s().inputTokens)} / {contextLimit() > 0 ? fmt(contextLimit()) : "?"} tokens
299+
<b>{s().usagePercentage.toFixed(1)}%</b> / {s().executeThreshold}%
300+
</text>
301+
<text fg={s().usagePercentage >= 80 ? t().error : s().usagePercentage >= 65 ? t().warning : t().accent}>
302+
{fmt(s().inputTokens)} / {contextLimit() > 0 ? fmt(contextLimit()) : "?"} tokens
298303
</text>
299304
</box>
300305

packages/plugin/src/tui/slots/sidebar-content.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,21 @@ const SidebarContent = (props: {
363363
{s() && s()!.inputTokens > 0 && (
364364
<box marginTop={1} flexDirection="column">
365365
{(s()?.contextLimit ?? 0) > 0 && (
366-
<box width="100%" flexDirection="row" justifyContent="flex-end">
366+
<box width="100%" flexDirection="row" justifyContent="space-between">
367+
{/* Left: current usage vs the per-model execute
368+
threshold (the value Magic Context compares
369+
against when scheduling historian / drops).
370+
"47.5% / 65%" tells the user how close they
371+
are to the next compaction trigger. */}
367372
<text fg={contextSummaryColor()}>
368-
<b>{s()!.usagePercentage.toFixed(1)}%</b> · {compactTokens(s()!.inputTokens)} / {compactTokens(s()!.contextLimit)}
373+
<b>{s()!.usagePercentage.toFixed(1)}%</b> / {s()!.executeThreshold}%
374+
</text>
375+
{/* Right: absolute token usage vs the model's
376+
full context window (separate from the
377+
execute threshold so users still know how
378+
much headroom remains beyond compaction). */}
379+
<text fg={contextSummaryColor()}>
380+
{compactTokens(s()!.inputTokens)} / {compactTokens(s()!.contextLimit)}
369381
</text>
370382
</box>
371383
)}

0 commit comments

Comments
 (0)