@linxiraos/pi-tui 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
|
@@ -6,7 +6,14 @@ import { formatKeyHint } from "../../app-keybindings";
|
|
|
6
6
|
import { col, node, span, text } from "../../native/describe";
|
|
7
7
|
import type { NativeNode } from "../../native/node";
|
|
8
8
|
import { Memo } from "../../native/memo";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
gradientEscape,
|
|
11
|
+
gradientLogo,
|
|
12
|
+
ZETA_LOGO,
|
|
13
|
+
ZETA_SUBLINE,
|
|
14
|
+
type ShineConfig,
|
|
15
|
+
logoNode,
|
|
16
|
+
} from "../../prompt/welcome";
|
|
10
17
|
|
|
11
18
|
export const SETUP_SPLASH_MS = 2600;
|
|
12
19
|
export const SETUP_TICK_MS = 33;
|
|
@@ -159,11 +166,12 @@ function resetCells(width: number, height: number): string[][] {
|
|
|
159
166
|
let diagonalEscapes: (string | undefined)[] = [];
|
|
160
167
|
|
|
161
168
|
/**
|
|
162
|
-
* Animated setup splash, in the spirit of the Zeta visual identity: the brand
|
|
163
|
-
* mark rendered
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* whole scene; the water
|
|
169
|
+
* Animated setup splash, in the spirit of the Zeta visual identity: the brand
|
|
170
|
+
* mark — the FIGlet-rendered Latin `Z`, the ASCII form of `Ζ` — with the live
|
|
171
|
+
* diagonal gradient + shine sweep, rising out of a rippling, gradient-lit water
|
|
172
|
+
* surface, under a faint twinkling starfield. The mark and water share one
|
|
173
|
+
* continuous gradient so the sweep reads across the whole scene; the water
|
|
174
|
+
* surface drifts each frame.
|
|
167
175
|
*/
|
|
168
176
|
export function renderSetupSplash(width: number, height: number, elapsedMs: number): string[] {
|
|
169
177
|
const w = Math.max(1, width);
|
|
@@ -265,7 +273,8 @@ export function describeSetupSplash(): NativeNode {
|
|
|
265
273
|
[
|
|
266
274
|
node("spacer", { grow: 1 }),
|
|
267
275
|
logoNode(LARGE_LOGO, true),
|
|
268
|
-
text([span("
|
|
276
|
+
text([span("Zeta", "strong")], { wrap: "none" }),
|
|
277
|
+
text([span("Linxira-Zeta", "dim")], { wrap: "none" }),
|
|
269
278
|
node("spacer", { grow: 1 }),
|
|
270
279
|
text([span(hint, "dim")], { wrap: "none" }),
|
|
271
280
|
],
|
|
@@ -277,7 +286,12 @@ export function describeSetupSplash(): NativeNode {
|
|
|
277
286
|
/** Centered fallback for windows too small to hold the full scene. */
|
|
278
287
|
function renderCompactSplash(width: number, height: number, phase: number, shine: ShineConfig): string[] {
|
|
279
288
|
const art = height >= 14 ? LARGE_LOGO : ZETA_LOGO;
|
|
280
|
-
const content = [
|
|
289
|
+
const content = [
|
|
290
|
+
...gradientLogo(art, phase, shine),
|
|
291
|
+
"",
|
|
292
|
+
theme.bold("Zeta"),
|
|
293
|
+
...ZETA_SUBLINE.map(row => theme.fg("dim", row)),
|
|
294
|
+
];
|
|
281
295
|
const start = Math.max(0, Math.floor((height - content.length) / 2));
|
|
282
296
|
const lines: string[] = [];
|
|
283
297
|
for (let y = 0; y < height; y++) {
|
|
@@ -43,7 +43,7 @@ import type { TspMeterMark, TspProps } from "@linxiraos/pi-wire";
|
|
|
43
43
|
import type { NativeNode, NativeUiEvent } from "../native/node";
|
|
44
44
|
import { col, node, span } from "../native/describe";
|
|
45
45
|
import { getContextMeterThresholds } from "../chrome/context-thresholds";
|
|
46
|
-
import { isNativeRendering } from "../native/state";
|
|
46
|
+
import { isNativeRendering, onNativeRenderingChange } from "../native/state";
|
|
47
47
|
import { getSeparator } from "./separators";
|
|
48
48
|
import type {
|
|
49
49
|
CollabStatus,
|
|
@@ -162,6 +162,10 @@ const WATCHER_FAILURE_POLL_TTL_MS = 5000;
|
|
|
162
162
|
const BRAND_FADE_MS = 450;
|
|
163
163
|
/** Repaint cadence while the brand fade is in flight (rust zeta's `FADE_FRAME`). */
|
|
164
164
|
const BRAND_FADE_FRAME_MS = 40;
|
|
165
|
+
/** Repaint cadence while a turn's active-processing window is open: keeps the
|
|
166
|
+
* status bar's clock-driven readouts (pi spinner, turn timer) animating when
|
|
167
|
+
* no other component is requesting renders. */
|
|
168
|
+
const ACTIVE_WINDOW_REPAINT_MS = 250;
|
|
165
169
|
|
|
166
170
|
/**
|
|
167
171
|
* Providers whose subscription quota is a single monthly bucket, so their
|
|
@@ -380,6 +384,7 @@ interface ContextUsageMemo {
|
|
|
380
384
|
length: number;
|
|
381
385
|
lastFingerprint: string | undefined;
|
|
382
386
|
modelContextWindow: number;
|
|
387
|
+
toolSchemasInContext: boolean;
|
|
383
388
|
contextUsageRevision: number;
|
|
384
389
|
toolSchemaMetadataRevision: number;
|
|
385
390
|
settingsRevision: number;
|
|
@@ -697,6 +702,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
697
702
|
#gitWatcherUnavailable = false;
|
|
698
703
|
#onBranchChange: (() => void) | null = null;
|
|
699
704
|
#disposed = false;
|
|
705
|
+
/** Unsubscribe for the native-rendering feed; re-arms the turn-window ticker when a TSP surface is revoked. */
|
|
706
|
+
#unwatchNativeRendering: (() => void) | undefined;
|
|
700
707
|
#autoCompactEnabled: boolean = true;
|
|
701
708
|
/** Pulse timer for the running-speculation indicator; live only while speculation runs. */
|
|
702
709
|
#speculationBlinkTimer: NodeJS.Timeout | undefined;
|
|
@@ -707,6 +714,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
707
714
|
#brandWorking = false;
|
|
708
715
|
/** Frame timer driving repaints while the brand fade is unsettled. */
|
|
709
716
|
#brandFadeTimer: NodeJS.Timeout | undefined;
|
|
717
|
+
/** Repaint ticker while a turn's active-processing window is open (see #syncActiveWindowRepaint). */
|
|
718
|
+
#activeWindowRepaintTimer: NodeJS.Timeout | undefined;
|
|
710
719
|
/** One wall-clock wakeup for the active model's next tariff change, including while idle. */
|
|
711
720
|
#pricingTimer: NodeJS.Timeout | undefined;
|
|
712
721
|
#pricingTimerCost: ModelCost | undefined;
|
|
@@ -774,9 +783,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
774
783
|
// than poison the fresh cache or advance its throttle.
|
|
775
784
|
#jjCacheGeneration = 0;
|
|
776
785
|
|
|
777
|
-
// PR lookup
|
|
786
|
+
// PR lookup cache. `#cachedPrContext` is the branch/repo `#cachedPr` was
|
|
787
|
+
// resolved for; it is never shown for any other branch. Expiry forces a
|
|
788
|
+
// refetch while keeping that branch's PR visible until the lookup lands.
|
|
778
789
|
#cachedPr: { number: number; url: string } | null | undefined = undefined;
|
|
779
790
|
#cachedPrContext: PrCacheContext | undefined = undefined;
|
|
791
|
+
#cachedPrExpired = false;
|
|
780
792
|
#prLookupInFlight = false;
|
|
781
793
|
#defaultBranch?: string;
|
|
782
794
|
#defaultBranchCwd: string | undefined = undefined;
|
|
@@ -829,6 +841,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
829
841
|
private readonly host: StatusLineHost<TSession>,
|
|
830
842
|
) {
|
|
831
843
|
this.#settings = host.getSettings();
|
|
844
|
+
this.#unwatchNativeRendering = onNativeRenderingChange(() => this.#syncActiveWindowRepaint());
|
|
832
845
|
}
|
|
833
846
|
|
|
834
847
|
#gitEnabled(): boolean {
|
|
@@ -935,6 +948,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
935
948
|
this.#invalidateSessionCaches();
|
|
936
949
|
this.#closeStaleActiveWindow();
|
|
937
950
|
}
|
|
951
|
+
// The ticker self-stops once its tick sees no open window for the
|
|
952
|
+
// newly-attached session; re-pointing focus at a session whose turn
|
|
953
|
+
// is still running must re-arm it, and no other path reliably fires
|
|
954
|
+
// here (closeStaleActiveWindow returns early while streaming, and
|
|
955
|
+
// the synthesized agent_start no-ops on an already-open window).
|
|
956
|
+
this.#syncActiveWindowRepaint();
|
|
938
957
|
this.invalidate();
|
|
939
958
|
}
|
|
940
959
|
|
|
@@ -1033,6 +1052,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1033
1052
|
meter.activeMs = 0;
|
|
1034
1053
|
meter.activeStartedAt = null;
|
|
1035
1054
|
this.#invalidateStatusLineRenderCache();
|
|
1055
|
+
this.#syncActiveWindowRepaint();
|
|
1036
1056
|
}
|
|
1037
1057
|
|
|
1038
1058
|
/**
|
|
@@ -1047,6 +1067,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1047
1067
|
if (meter.activeStartedAt !== null) return;
|
|
1048
1068
|
meter.activeStartedAt = Date.now();
|
|
1049
1069
|
this.#invalidateStatusLineRenderCache();
|
|
1070
|
+
this.#syncActiveWindowRepaint();
|
|
1050
1071
|
}
|
|
1051
1072
|
|
|
1052
1073
|
/**
|
|
@@ -1061,6 +1082,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1061
1082
|
meter.activeMs += Math.max(0, Date.now() - meter.activeStartedAt);
|
|
1062
1083
|
meter.activeStartedAt = null;
|
|
1063
1084
|
this.#invalidateStatusLineRenderCache();
|
|
1085
|
+
this.#syncActiveWindowRepaint();
|
|
1064
1086
|
}
|
|
1065
1087
|
|
|
1066
1088
|
/**
|
|
@@ -1290,8 +1312,11 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1290
1312
|
this.#onBranchChange = null;
|
|
1291
1313
|
this.#stopSpeculationBlink();
|
|
1292
1314
|
this.#stopBrandFadeTimer();
|
|
1315
|
+
this.#stopActiveWindowRepaint();
|
|
1293
1316
|
this.#stopPricingTimer();
|
|
1294
1317
|
this.#clearUsageStartTimer();
|
|
1318
|
+
this.#unwatchNativeRendering?.();
|
|
1319
|
+
this.#unwatchNativeRendering = undefined;
|
|
1295
1320
|
this.#onCodexResetFireworks = undefined;
|
|
1296
1321
|
this.#onResetExpiryNotice = undefined;
|
|
1297
1322
|
this.#codexResetSnapshots.clear();
|
|
@@ -1395,6 +1420,40 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1395
1420
|
this.#brandFadeTimer = undefined;
|
|
1396
1421
|
}
|
|
1397
1422
|
|
|
1423
|
+
/**
|
|
1424
|
+
* Keep the status bar repainting while a turn's active-processing window
|
|
1425
|
+
* is open. The `pi` segment's spinner and turn timer are painted from the
|
|
1426
|
+
* wall clock at render time, so they only animate when frames flow; during
|
|
1427
|
+
* a quiet turn the working-row loader's 80 ms tick is normally the sole
|
|
1428
|
+
* repaint source, and paths that stop or bypass that loader mid-turn (an
|
|
1429
|
+
* error banner, a stale retry/compaction loader reference) froze the
|
|
1430
|
+
* spinner until the next keystroke. The status line owns the active
|
|
1431
|
+
* window, so it owns a repaint source for it; a native surface clocks its
|
|
1432
|
+
* own spinner and needs none.
|
|
1433
|
+
*/
|
|
1434
|
+
#syncActiveWindowRepaint(): void {
|
|
1435
|
+
if (this.#disposed || isNativeRendering() || this.getTurnElapsedMs() === null) {
|
|
1436
|
+
this.#stopActiveWindowRepaint();
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
if (this.#activeWindowRepaintTimer) return;
|
|
1440
|
+
const timer = setInterval(() => {
|
|
1441
|
+
if (this.#disposed || isNativeRendering() || this.getTurnElapsedMs() === null) {
|
|
1442
|
+
this.#stopActiveWindowRepaint();
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
this.#onBranchChange?.();
|
|
1446
|
+
}, ACTIVE_WINDOW_REPAINT_MS);
|
|
1447
|
+
timer.unref?.();
|
|
1448
|
+
this.#activeWindowRepaintTimer = timer;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
#stopActiveWindowRepaint(): void {
|
|
1452
|
+
if (!this.#activeWindowRepaintTimer) return;
|
|
1453
|
+
clearInterval(this.#activeWindowRepaintTimer);
|
|
1454
|
+
this.#activeWindowRepaintTimer = undefined;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1398
1457
|
#stopPricingTimer(): void {
|
|
1399
1458
|
clearTimeout(this.#pricingTimer);
|
|
1400
1459
|
this.#pricingTimer = undefined;
|
|
@@ -1457,8 +1516,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1457
1516
|
// was designed to avoid. Explicit Git/repository invalidation (watcher
|
|
1458
1517
|
// HEAD-move, cwd/repo switch) goes through {@link invalidateGitCaches}.
|
|
1459
1518
|
// A tool may open, close, or merge a PR without moving HEAD. Expire the
|
|
1460
|
-
// settled PR
|
|
1461
|
-
this.#
|
|
1519
|
+
// settled PR on ordinary activity while leaving HEAD work intact.
|
|
1520
|
+
this.#cachedPrExpired = true;
|
|
1462
1521
|
}
|
|
1463
1522
|
#invalidateSessionCaches(): void {
|
|
1464
1523
|
this.#clearUsageStartTimer();
|
|
@@ -1489,7 +1548,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1489
1548
|
this.#branchResolveActive = undefined;
|
|
1490
1549
|
this.#branchLastFetch = undefined;
|
|
1491
1550
|
this.#branchCacheGeneration++;
|
|
1492
|
-
this.#
|
|
1551
|
+
this.#cachedPrExpired = true;
|
|
1493
1552
|
// A HEAD move (commit, checkout, reset) changes the dirty counts; keep the
|
|
1494
1553
|
// stale counts on screen but refetch on the next render. The generation
|
|
1495
1554
|
// bump stops an in-flight pre-move result from re-stamping freshness.
|
|
@@ -1810,11 +1869,16 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1810
1869
|
const branch = this.#getBranchLabel(activeRepoCache, this.#resolveRepository(activeRepoCache));
|
|
1811
1870
|
const currentContext = branch ? createPrCacheContext(branch, this.#cachedBranchRepoId ?? null) : null;
|
|
1812
1871
|
|
|
1813
|
-
if (canReuseCachedPr(this.#cachedPr, this.#cachedPrContext, currentContext)) {
|
|
1872
|
+
if (!this.#cachedPrExpired && canReuseCachedPr(this.#cachedPr, this.#cachedPrContext, currentContext)) {
|
|
1814
1873
|
return this.#cachedPr ?? null;
|
|
1815
1874
|
}
|
|
1816
1875
|
|
|
1817
|
-
|
|
1876
|
+
// Keep an expired PR visible during the refetch only on the branch it
|
|
1877
|
+
// was resolved for; after a checkout the old PR must not linger.
|
|
1878
|
+
const stalePr =
|
|
1879
|
+
currentContext && isSamePrCacheContext(this.#cachedPrContext, currentContext)
|
|
1880
|
+
? (this.#cachedPr ?? null)
|
|
1881
|
+
: null;
|
|
1818
1882
|
|
|
1819
1883
|
if (!branch) {
|
|
1820
1884
|
this.#cachedPr = null;
|
|
@@ -1822,10 +1886,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1822
1886
|
return null;
|
|
1823
1887
|
}
|
|
1824
1888
|
|
|
1825
|
-
//
|
|
1826
|
-
if (branch === "detached" || this.#isDefaultBranch(branch, gitCwd)
|
|
1827
|
-
|
|
1828
|
-
}
|
|
1889
|
+
// Detached HEAD and the default branch never carry a PR; no lookup.
|
|
1890
|
+
if (branch === "detached" || this.#isDefaultBranch(branch, gitCwd)) return null;
|
|
1891
|
+
if (this.#prLookupInFlight) return stalePr;
|
|
1829
1892
|
|
|
1830
1893
|
this.#prLookupInFlight = true;
|
|
1831
1894
|
const lookupContext = currentContext;
|
|
@@ -1847,6 +1910,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1847
1910
|
if (lookupContext && isSamePrCacheContext(latestContext, lookupContext)) {
|
|
1848
1911
|
this.#cachedPr = value;
|
|
1849
1912
|
this.#cachedPrContext = lookupContext;
|
|
1913
|
+
this.#cachedPrExpired = false;
|
|
1850
1914
|
}
|
|
1851
1915
|
};
|
|
1852
1916
|
try {
|
|
@@ -1980,6 +2044,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1980
2044
|
* cadence while a late successful fetch can still refresh the cached segment.
|
|
1981
2045
|
*/
|
|
1982
2046
|
refreshUsageInBackground(): void {
|
|
2047
|
+
if (this.#disposed) return;
|
|
1983
2048
|
const now = Date.now();
|
|
1984
2049
|
const session = this.session;
|
|
1985
2050
|
const usageContextKey = this.#getUsageContextKey(session);
|
|
@@ -2407,6 +2472,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2407
2472
|
getCachedContextBreakdown(): { usedTokens: number; contextWindow: number } {
|
|
2408
2473
|
const messages = this.session.messages ?? EMPTY_MESSAGES;
|
|
2409
2474
|
const modelContextWindow = this.session.model?.contextWindow ?? 0;
|
|
2475
|
+
const toolSchemasInContext = this.session.model?.toolSchemasInContext !== false;
|
|
2410
2476
|
const length = messages.length;
|
|
2411
2477
|
const lastFingerprint = length > 0 ? messageFingerprint(messages[length - 1]!) : undefined;
|
|
2412
2478
|
// Bumps when the in-flight pending snapshot is set/cleared. Without it a
|
|
@@ -2428,6 +2494,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2428
2494
|
cache.length === length &&
|
|
2429
2495
|
cache.lastFingerprint === lastFingerprint &&
|
|
2430
2496
|
cache.modelContextWindow === modelContextWindow &&
|
|
2497
|
+
cache.toolSchemasInContext === toolSchemasInContext &&
|
|
2431
2498
|
cache.contextUsageRevision === contextUsageRevision &&
|
|
2432
2499
|
cache.systemPromptRef === systemPrompt &&
|
|
2433
2500
|
cache.toolsRef === tools &&
|
|
@@ -2448,6 +2515,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2448
2515
|
length,
|
|
2449
2516
|
lastFingerprint,
|
|
2450
2517
|
modelContextWindow,
|
|
2518
|
+
toolSchemasInContext,
|
|
2451
2519
|
contextUsageRevision,
|
|
2452
2520
|
toolSchemaMetadataRevision,
|
|
2453
2521
|
settingsRevision,
|
|
@@ -143,6 +143,7 @@ export function computeCompactionBoundaries(
|
|
|
143
143
|
|
|
144
144
|
/** Stable inputs used to cache non-message token estimates. */
|
|
145
145
|
export interface NonMessageTokenSource {
|
|
146
|
+
readonly model?: Model;
|
|
146
147
|
readonly systemPrompt?: readonly string[];
|
|
147
148
|
readonly agent?: {
|
|
148
149
|
readonly state?: {
|
|
@@ -272,30 +273,32 @@ export function estimateToolSchemaTokens(tools: ToolSchemaSource, tokenizer: Tok
|
|
|
272
273
|
return tokens;
|
|
273
274
|
}
|
|
274
275
|
|
|
276
|
+
/** Count schemas carried on every request, excluding server-side deferred catalogs. */
|
|
277
|
+
export function estimateContextToolSchemaTokens(
|
|
278
|
+
tools: ToolSchemaSource,
|
|
279
|
+
tokenizer: Tokenizer,
|
|
280
|
+
model: Model | undefined,
|
|
281
|
+
sourceRevision = 0,
|
|
282
|
+
): number {
|
|
283
|
+
return model?.toolSchemasInContext === false ? 0 : estimateToolSchemaTokens(tools, tokenizer, sourceRevision);
|
|
284
|
+
}
|
|
285
|
+
|
|
275
286
|
/**
|
|
276
287
|
* Compute just the NON-MESSAGE token total: system prompt (with its skills
|
|
277
288
|
* section subtracted, since skills are tokenized separately) + system context
|
|
278
289
|
* (the rest of the system-prompt array) + tools + skills.
|
|
279
290
|
*
|
|
280
291
|
* Exposed so callers like `StatusLineComponent` can cache the non-message
|
|
281
|
-
* total separately from the message total.
|
|
282
|
-
*
|
|
283
|
-
* streaming turn. Splitting the two lets the caller refresh each on its own
|
|
284
|
-
* cadence — non-message recomputed only when the inputs identity changes,
|
|
285
|
-
* messages walked incrementally as new entries append.
|
|
292
|
+
* total separately from the growing message total. A model switch can change
|
|
293
|
+
* which tool schemas occupy context without changing the roster.
|
|
286
294
|
*/
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// both, and #estimateStoredContextTokens adds a third. Memoize on the identity
|
|
291
|
-
// of the three input arrays so the expensive parts (system-prompt tokenization
|
|
292
|
-
// and the per-tool JSON.stringify(toolWireSchema) inside estimateToolSchemaTokens)
|
|
293
|
-
// run at most once per input change rather than per call. The identity keys are
|
|
294
|
-
// the same stable references the StatusLineComponent cache already trusts
|
|
295
|
-
// (setSystemPrompt/setTools replace the array reference rather than mutating it).
|
|
295
|
+
// The per-turn threshold and compaction paths call these helpers repeatedly.
|
|
296
|
+
// Cache by prompt, roster, skills, tokenizer, revision, and model schema policy;
|
|
297
|
+
// setSystemPrompt/setTools replace their arrays rather than mutating them.
|
|
296
298
|
interface NonMessageTokenCache {
|
|
297
299
|
systemPromptRef: readonly string[];
|
|
298
300
|
toolsRef: ToolSchemaSource;
|
|
301
|
+
toolSchemasInContext: boolean;
|
|
299
302
|
toolsRevision: number;
|
|
300
303
|
sourceRevision: number;
|
|
301
304
|
skillful: boolean;
|
|
@@ -328,6 +331,7 @@ function nonMessageTokenCacheEntry(
|
|
|
328
331
|
const cachedSession: CachedNonMessageTokenSource = session;
|
|
329
332
|
const systemPromptRef = session.systemPrompt ?? EMPTY_STRING_PARTS;
|
|
330
333
|
const toolsRef = session.agent?.state?.tools ?? EMPTY_TOOLS;
|
|
334
|
+
const toolSchemasInContext = session.model?.toolSchemasInContext !== false;
|
|
331
335
|
const toolsRevision = getToolSchemaMetadataRevision(toolsRef);
|
|
332
336
|
const skillsRef = session.renderedSkills ?? session.skills ?? EMPTY_SKILLS;
|
|
333
337
|
let entry = cachedSession[NON_MESSAGE_TOKEN_CACHE];
|
|
@@ -335,6 +339,7 @@ function nonMessageTokenCacheEntry(
|
|
|
335
339
|
entry &&
|
|
336
340
|
entry.systemPromptRef === systemPromptRef &&
|
|
337
341
|
entry.toolsRef === toolsRef &&
|
|
342
|
+
entry.toolSchemasInContext === toolSchemasInContext &&
|
|
338
343
|
entry.toolsRevision === toolsRevision &&
|
|
339
344
|
entry.sourceRevision === sourceRevision &&
|
|
340
345
|
entry.skillsRef === skillsRef &&
|
|
@@ -345,6 +350,7 @@ function nonMessageTokenCacheEntry(
|
|
|
345
350
|
entry = {
|
|
346
351
|
systemPromptRef,
|
|
347
352
|
toolsRef,
|
|
353
|
+
toolSchemasInContext,
|
|
348
354
|
toolsRevision,
|
|
349
355
|
sourceRevision,
|
|
350
356
|
skillful: true,
|
|
@@ -368,7 +374,7 @@ export function computeNonMessageTokens(
|
|
|
368
374
|
const tools = session.agent?.state?.tools ?? EMPTY_TOOLS;
|
|
369
375
|
const tokens =
|
|
370
376
|
tokenizer.countTokens(Array.from(systemPromptParts, part => part ?? "")) +
|
|
371
|
-
|
|
377
|
+
estimateContextToolSchemaTokens(tools, tokenizer, session.model, sourceRevision);
|
|
372
378
|
entry.tokens = tokens;
|
|
373
379
|
return tokens;
|
|
374
380
|
}
|
|
@@ -400,7 +406,7 @@ export function computeNonMessageBreakdown(
|
|
|
400
406
|
renderedSkills(session.renderedSkills ?? session.skills ?? EMPTY_SKILLS, tools),
|
|
401
407
|
tokenizer,
|
|
402
408
|
);
|
|
403
|
-
const toolsTokens =
|
|
409
|
+
const toolsTokens = estimateContextToolSchemaTokens(tools, tokenizer, session.model, sourceRevision);
|
|
404
410
|
const systemPromptParts = session.systemPrompt ?? EMPTY_STRING_PARTS;
|
|
405
411
|
const systemContextTokens = tokenizer.countTokens(Array.from(systemPromptParts.slice(1), part => part ?? ""));
|
|
406
412
|
const systemPromptTokens = Math.max(0, tokenizer.countTokens(systemPromptParts[0] ?? "") - skillsTokens);
|
|
@@ -742,86 +748,183 @@ function contextMeterParts(
|
|
|
742
748
|
}
|
|
743
749
|
|
|
744
750
|
/**
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
|
|
751
|
+
* One line of the native `/context` breakdown: a swatch in the part's bar
|
|
752
|
+
* color, its name, its tokens and its share of the window; the exact count
|
|
753
|
+
* is its tooltip.
|
|
754
|
+
*/
|
|
755
|
+
function contextShareRow(
|
|
756
|
+
key: string,
|
|
757
|
+
role: string,
|
|
758
|
+
swatch: TspSpan,
|
|
759
|
+
label: string,
|
|
760
|
+
tokens: number,
|
|
761
|
+
window: number,
|
|
762
|
+
): NativeNode {
|
|
763
|
+
return node(
|
|
764
|
+
"row",
|
|
765
|
+
{ role, align: "center", title: `${label} · ${tokens.toLocaleString("en-US")} tokens` },
|
|
766
|
+
[
|
|
767
|
+
text([swatch], { role: "zeta.context.swatch", wrap: "none" }),
|
|
768
|
+
text(label, { role: "zeta.context.label", grow: 1, wrap: "none", truncate: "end" }),
|
|
769
|
+
text(formatNumber(tokens), { role: "zeta.context.tokens", wrap: "none" }),
|
|
770
|
+
text(percentString(tokens, window), { role: "zeta.context.share", wrap: "none" }),
|
|
771
|
+
],
|
|
772
|
+
key,
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* The snapcompact estimate as a titled group of lines (what is imaged, how,
|
|
778
|
+
* and what it saves), or a note saying why it is inactive; `undefined` when
|
|
779
|
+
* no snapcompact setting is on.
|
|
780
|
+
*/
|
|
781
|
+
function describeSnapcompact(breakdown: ContextBreakdown): NativeNode | undefined {
|
|
782
|
+
const snap = breakdown.snapcompact;
|
|
783
|
+
if (!snap) return undefined;
|
|
784
|
+
const head = text([span("Snapcompact", "strong"), span(" · estimated savings on the wire", "dim")], {
|
|
785
|
+
role: "zeta.context.snap.head",
|
|
786
|
+
});
|
|
787
|
+
if (!snap.visionCapable) {
|
|
788
|
+
const note = text("Inactive: the model has no image input.", { role: "zeta.context.snap.note" });
|
|
789
|
+
return node("col", { role: "zeta.context.snap" }, [head, note], "snapcompact");
|
|
790
|
+
}
|
|
791
|
+
const lines: { label: string; detail: string; value?: TspSpan }[] = [];
|
|
792
|
+
const sp = snap.systemPrompt;
|
|
793
|
+
if (sp) {
|
|
794
|
+
const label = `System prompt · ${sp.scope === "agents-md" ? "AGENTS.md" : "all"}`;
|
|
795
|
+
if (sp.applied) {
|
|
796
|
+
lines.push({
|
|
797
|
+
label,
|
|
798
|
+
detail: `${formatNumber(sp.textTokens)} text → ${sp.frames} frame${sp.frames === 1 ? "" : "s"} ≈ ${formatNumber(sp.imageTokens)}`,
|
|
799
|
+
value: span(`−${formatNumber(sp.savedTokens)}`, "success"),
|
|
800
|
+
});
|
|
801
|
+
} else {
|
|
802
|
+
const reason =
|
|
803
|
+
sp.reason === "budget"
|
|
804
|
+
? "image budget exhausted"
|
|
805
|
+
: sp.reason === "empty"
|
|
806
|
+
? "nothing to image"
|
|
807
|
+
: "frames would not save tokens";
|
|
808
|
+
lines.push({ label, detail: `Stays text: ${reason}` });
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const tr = snap.toolResults;
|
|
812
|
+
if (tr) {
|
|
813
|
+
lines.push(
|
|
814
|
+
tr.swapped > 0
|
|
815
|
+
? {
|
|
816
|
+
label: "Tool results",
|
|
817
|
+
detail: `${tr.swapped} of ${tr.total} imaged · ${formatNumber(tr.textTokens)} text → ${tr.frames} frames ≈ ${formatNumber(tr.imageTokens)}`,
|
|
818
|
+
value: span(`−${formatNumber(tr.savedTokens)}`, "success"),
|
|
819
|
+
}
|
|
820
|
+
: { label: "Tool results", detail: `None imaged · ${tr.total} in history` },
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
if (snap.savedTokens > 0) {
|
|
824
|
+
lines.push({
|
|
825
|
+
label: "Next request",
|
|
826
|
+
detail: "tokens on the wire",
|
|
827
|
+
value: span(`~${formatNumber(Math.max(0, breakdown.usedTokens - snap.savedTokens))}`, "strong"),
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
const rows = lines.map((line, index) =>
|
|
831
|
+
node(
|
|
832
|
+
"row",
|
|
833
|
+
{ role: "zeta.context.snap.row", align: "baseline" },
|
|
834
|
+
[
|
|
835
|
+
text(line.label, { role: "zeta.context.label", wrap: "none" }),
|
|
836
|
+
text(line.detail, { role: "zeta.context.detail", grow: 1, wrap: "none", truncate: "end" }),
|
|
837
|
+
...(line.value ? [text([line.value], { role: "zeta.context.tokens", wrap: "none" })] : []),
|
|
838
|
+
],
|
|
839
|
+
`snap-${index}`,
|
|
840
|
+
),
|
|
841
|
+
);
|
|
842
|
+
return node("col", { role: "zeta.context.snap" }, [head, ...rows], "snapcompact");
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* The `/context` body for terminals that draw `meter`: the tokens used as
|
|
847
|
+
* the figure that leads, beside the room left before auto-compaction; one
|
|
848
|
+
* stacked bar of the parts (categories, free space as the empty track, the
|
|
849
|
+
* hatched buffer) ticked where compaction fires; the breakdown in the bar's
|
|
850
|
+
* order with free space and the buffer under it; then the snapcompact
|
|
851
|
+
* estimate when one is on. {@link contextUsageHead} names the model.
|
|
749
852
|
*/
|
|
750
853
|
function describeContextFrame(breakdown: ContextBreakdown): NativeNode {
|
|
751
854
|
const { contextWindow: window, usedTokens, freeTokens, autoCompactBufferTokens, thresholdTokens } = breakdown;
|
|
752
855
|
const parts = contextMeterParts(breakdown);
|
|
753
856
|
const used = Math.min(1, usedTokens / window);
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
857
|
+
const room =
|
|
858
|
+
thresholdTokens === undefined
|
|
859
|
+
? `${formatNumber(freeTokens)} free`
|
|
860
|
+
: usedTokens >= thresholdTokens
|
|
861
|
+
? `Past auto-compaction at ${percentString(thresholdTokens, window, 0)}`
|
|
862
|
+
: `${formatNumber(thresholdTokens - usedTokens)} left before auto-compaction at ${percentString(thresholdTokens, window, 0)}`;
|
|
863
|
+
const summary = node(
|
|
864
|
+
"row",
|
|
865
|
+
{ role: "zeta.context.summary", align: "baseline", wrap: true },
|
|
866
|
+
[
|
|
867
|
+
text(
|
|
868
|
+
[
|
|
869
|
+
span(formatNumber(usedTokens), "strong"),
|
|
870
|
+
span(` of ${formatNumber(window)} tokens · ${percentString(usedTokens, window, 0)}`, "dim"),
|
|
871
|
+
],
|
|
872
|
+
{ role: "zeta.context.used", grow: 1, wrap: "none" },
|
|
873
|
+
),
|
|
874
|
+
text(room, { role: "zeta.context.room", wrap: "none" }),
|
|
875
|
+
],
|
|
876
|
+
"summary",
|
|
877
|
+
);
|
|
775
878
|
const marks =
|
|
776
879
|
thresholdTokens !== undefined
|
|
777
880
|
? [
|
|
778
881
|
{
|
|
779
882
|
at: thresholdTokens / window,
|
|
780
883
|
tone: "warning" as const,
|
|
781
|
-
title: `Auto-compaction at ${percentString(thresholdTokens, window, 0)}
|
|
884
|
+
title: `Auto-compaction at ${percentString(thresholdTokens, window, 0)} · ${formatNumber(thresholdTokens)} tokens`,
|
|
782
885
|
},
|
|
783
886
|
]
|
|
784
887
|
: undefined;
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
"bar",
|
|
888
|
+
const bar = node(
|
|
889
|
+
"meter",
|
|
890
|
+
{ value: used, style: "bar", size: "lg", parts, ...(marks ? { marks } : {}), aria: "Context usage by category" },
|
|
891
|
+
undefined,
|
|
892
|
+
"bar",
|
|
893
|
+
);
|
|
894
|
+
const shares = breakdown.categories.map(category =>
|
|
895
|
+
contextShareRow(
|
|
896
|
+
category.id,
|
|
897
|
+
"zeta.context.cat",
|
|
898
|
+
span("■", category.color),
|
|
899
|
+
category.label,
|
|
900
|
+
category.tokens,
|
|
901
|
+
window,
|
|
800
902
|
),
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
if (
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
"
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
"
|
|
903
|
+
);
|
|
904
|
+
shares.push(contextShareRow("free", "zeta.context.cat.free", span("□", "dim"), "Free space", freeTokens, window));
|
|
905
|
+
if (autoCompactBufferTokens > 0) {
|
|
906
|
+
shares.push(
|
|
907
|
+
contextShareRow(
|
|
908
|
+
"buffer",
|
|
909
|
+
"zeta.context.cat.buffer",
|
|
910
|
+
span("▨", "warning"),
|
|
911
|
+
"Autocompact buffer",
|
|
912
|
+
autoCompactBufferTokens,
|
|
913
|
+
window,
|
|
810
914
|
),
|
|
811
915
|
);
|
|
812
916
|
}
|
|
813
|
-
|
|
917
|
+
const children = [summary, bar, node("col", { role: "zeta.context.cats" }, shares, "cats")];
|
|
918
|
+
const snap = describeSnapcompact(breakdown);
|
|
919
|
+
if (snap) children.push(snap);
|
|
920
|
+
return col(children, { role: "zeta.context" });
|
|
814
921
|
}
|
|
815
922
|
|
|
816
|
-
/** The `/context` title: the model
|
|
923
|
+
/** The `/context` title: the model once one is selected (the body leads with its window). */
|
|
817
924
|
export function contextUsageHead(breakdown: ContextBreakdown): TspText {
|
|
818
925
|
if (breakdown.contextWindow <= 0) return [span("Context usage", "strong")];
|
|
819
926
|
const modelName = breakdown.model?.name ?? breakdown.model?.id ?? "no model";
|
|
820
|
-
return [
|
|
821
|
-
span("Context", "strong"),
|
|
822
|
-
span(` · ${modelName}`, "muted"),
|
|
823
|
-
span(` · ${formatNumber(breakdown.contextWindow).toLowerCase()}`, "dim"),
|
|
824
|
-
];
|
|
927
|
+
return [span("Context", "strong"), span(` · ${modelName}`, "muted")];
|
|
825
928
|
}
|
|
826
929
|
|
|
827
930
|
/**
|