@linxiraos/pi-tui 1.1.27 → 1.2.0
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 +47 -463
- package/README.md +7 -5
- package/dist/types/app-keybindings.d.ts +1 -1
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +2 -2
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +5 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +2 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +15 -2
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +15 -4
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +12 -44
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/markdown.d.ts +36 -3
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +1 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/backend.d.ts +3 -2
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +3 -4
- package/dist/types/native/overlay.d.ts +5 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +2 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +11 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +23 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/usage-dashboard.d.ts +8 -4
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer.d.ts +1 -1
- package/dist/types/prompt/custom-editor.d.ts +11 -8
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +4 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +6 -1
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +27 -35
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +22 -6
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +0 -4
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +31 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/package.json +13 -9
- package/src/app-keybindings.d.ts +1 -1
- package/src/app-keybindings.ts +1 -1
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +26 -5
- package/src/apps/git/git-tui.ts +2 -2
- package/src/apps/git/help.ts +29 -20
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +5 -0
- package/src/chat/assistant-message.ts +219 -50
- package/src/chat/chat-transcript-builder.d.ts +2 -0
- package/src/chat/chat-transcript-builder.ts +5 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +80 -6
- package/src/chat/user-message.d.ts +15 -2
- package/src/chat/user-message.ts +55 -3
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +93 -13
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +15 -4
- package/src/components/editor.ts +294 -46
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +12 -44
- package/src/components/image.ts +47 -151
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/markdown.d.ts +36 -3
- package/src/components/markdown.ts +433 -318
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +1 -1
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/key-hint-format.d.ts +1 -1
- package/src/key-hint-format.ts +1 -1
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +4 -4
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/backend.d.ts +3 -2
- package/src/native/backend.ts +174 -18
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +3 -4
- package/src/native/encode.ts +58 -31
- package/src/native/overlay.d.ts +5 -0
- package/src/native/overlay.ts +13 -0
- package/src/native/reconcile.ts +4 -0
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +44 -29
- package/src/overlays/agent-transcript-viewer.ts +91 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +7 -10
- package/src/overlays/btw-history-panel.ts +12 -1
- package/src/overlays/copy-selector.ts +30 -8
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/jobs-panel.ts +28 -46
- package/src/overlays/model-browser.ts +341 -110
- package/src/overlays/model-hub.ts +300 -49
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/plan-review-overlay.ts +194 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +2 -2
- package/src/overlays/rewind-selector.ts +86 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +4 -3
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +100 -50
- package/src/overlays/usage-display.ts +10 -4
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer.ts +4 -15
- package/src/prompt/custom-editor.ts +54 -26
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +30 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/tips.txt +6 -5
- package/src/prompt/welcome.ts +55 -12
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +11 -0
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/types.ts +1 -0
- package/src/status-line/component.ts +292 -78
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +27 -35
- package/src/terminal-capabilities.ts +75 -189
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +22 -6
- package/src/terminal.ts +139 -37
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +6 -1
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.ts +92 -42
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +149 -50
- package/src/tools/task.d.ts +0 -4
- package/src/tools/task.ts +112 -59
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +31 -0
- package/src/tui.ts +309 -160
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.ts +8 -0
|
@@ -27,7 +27,7 @@ import type {
|
|
|
27
27
|
} from "./host";
|
|
28
28
|
import type { Editor } from "../components/editor";
|
|
29
29
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../theme/session-color";
|
|
30
|
-
import { sanitizeStatusText } from "../chrome/shared";
|
|
30
|
+
import { sanitizeHookStatusText, sanitizeStatusText } from "../chrome/shared";
|
|
31
31
|
import { getThemeEpoch, theme } from "../theme";
|
|
32
32
|
import { type CompactionBoundaries, EMPTY_STRING_PARTS, getToolSchemaMetadataRevision } from "./context-usage";
|
|
33
33
|
import {
|
|
@@ -51,9 +51,12 @@ import type {
|
|
|
51
51
|
ComposerFactsSource,
|
|
52
52
|
EffectiveStatusLineSettings,
|
|
53
53
|
SegmentView,
|
|
54
|
+
SeparatorDef,
|
|
54
55
|
StatusLineSegmentId,
|
|
55
56
|
StatusLineSegmentOptions,
|
|
57
|
+
StatusLineSeparatorStyle,
|
|
56
58
|
StatusLineSettings,
|
|
59
|
+
StatusResetExpiry,
|
|
57
60
|
} from "./types";
|
|
58
61
|
|
|
59
62
|
/** What a click on a native status segment asks zeta to open. */
|
|
@@ -81,9 +84,9 @@ const PINNED_NATIVE_PRIORITY = 1000;
|
|
|
81
84
|
/**
|
|
82
85
|
* Segments a TSP terminal shows outside the composer's facts: the model chip,
|
|
83
86
|
* the context hairline and usage text (context, cost), Tern's pane header
|
|
84
|
-
* (path
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
+
* (path), the tab title (session name, PR), the HUD pills (subagents), the
|
|
88
|
+
* editor (vim) and the brand (`pi`; while focus-proxied, the viewed agent is
|
|
89
|
+
* the composer's viewing header).
|
|
87
90
|
*/
|
|
88
91
|
const COMPOSER_HOMED_SEGMENTS: Partial<Record<StatusLineSegmentId, true>> = {
|
|
89
92
|
pi: true,
|
|
@@ -92,7 +95,6 @@ const COMPOSER_HOMED_SEGMENTS: Partial<Record<StatusLineSegmentId, true>> = {
|
|
|
92
95
|
context_total: true,
|
|
93
96
|
cost: true,
|
|
94
97
|
path: true,
|
|
95
|
-
git: true,
|
|
96
98
|
pr: true,
|
|
97
99
|
session_name: true,
|
|
98
100
|
subagents: true,
|
|
@@ -222,12 +224,51 @@ function reportMatchesExactIdentity(report: UsageReport, identity: OAuthAccountI
|
|
|
222
224
|
// Context-usage memo
|
|
223
225
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
224
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Serialized length of tool-call arguments, memoized per arguments object.
|
|
229
|
+
* Streaming providers may grow the same object in place (at any depth), so
|
|
230
|
+
* each entry keeps a shape check — key/element counts plus string and number
|
|
231
|
+
* lengths over the whole tree — and is recomputed when it differs, without
|
|
232
|
+
* re-serializing the whole (possibly multi-KB edit/write) payload on every
|
|
233
|
+
* status-line rebuild.
|
|
234
|
+
*/
|
|
235
|
+
const toolArgumentsLengthCache = new WeakMap<object, { check: number; length: number }>();
|
|
236
|
+
|
|
237
|
+
/** Past this depth the check stops descending; JSON.stringify rejects real cycles anyway. */
|
|
238
|
+
const SHAPE_CHECK_MAX_DEPTH = 32;
|
|
239
|
+
|
|
240
|
+
function toolArgumentsShapeCheck(value: unknown, depth = 0): number {
|
|
241
|
+
if (typeof value === "string") return value.length + 1;
|
|
242
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value).length + 1;
|
|
243
|
+
if (value === null || typeof value !== "object" || depth >= SHAPE_CHECK_MAX_DEPTH) return 1;
|
|
244
|
+
let check = 1;
|
|
245
|
+
for (const child of Object.values(value)) check += 1 + toolArgumentsShapeCheck(child, depth + 1);
|
|
246
|
+
return check;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function toolArgumentsLength(args: unknown): number {
|
|
250
|
+
if (typeof args === "string") return args.length;
|
|
251
|
+
if (args === null || typeof args !== "object") return String(args).length;
|
|
252
|
+
const check = toolArgumentsShapeCheck(args);
|
|
253
|
+
const cached = toolArgumentsLengthCache.get(args);
|
|
254
|
+
if (cached !== undefined && cached.check === check) return cached.length;
|
|
255
|
+
let length: number;
|
|
256
|
+
try {
|
|
257
|
+
length =
|
|
258
|
+
JSON.stringify(args, (_key, value) => (typeof value === "bigint" ? value.toString() : value))?.length ?? 0;
|
|
259
|
+
} catch {
|
|
260
|
+
length = String(args).length;
|
|
261
|
+
}
|
|
262
|
+
toolArgumentsLengthCache.set(args, { check, length });
|
|
263
|
+
return length;
|
|
264
|
+
}
|
|
265
|
+
|
|
225
266
|
/**
|
|
226
267
|
* Cheap structural fingerprint of a message's tokenizable content. O(blocks) —
|
|
227
|
-
* only reads string `.length
|
|
228
|
-
* Detects
|
|
229
|
-
* so the cached `getContextUsage()` result is
|
|
230
|
-
* the numbers it depends on change.
|
|
268
|
+
* only reads string `.length`, primitives, and memoized tool-argument sizes;
|
|
269
|
+
* never copies. Detects growth of the streaming tail (in-place text appends,
|
|
270
|
+
* freshly parsed tool arguments) so the cached `getContextUsage()` result is
|
|
271
|
+
* recomputed when — and only when — the numbers it depends on change.
|
|
231
272
|
*/
|
|
232
273
|
function messageFingerprint(msg: AgentMessage): string {
|
|
233
274
|
const role = (msg as { role?: string }).role ?? "";
|
|
@@ -309,15 +350,7 @@ function messageFingerprint(msg: AgentMessage): string {
|
|
|
309
350
|
redactedLen += b.data.length;
|
|
310
351
|
} else if (b.type === "toolCall") {
|
|
311
352
|
if (typeof b.name === "string") textLen += b.name.length;
|
|
312
|
-
if (b.arguments !== undefined)
|
|
313
|
-
try {
|
|
314
|
-
textLen += JSON.stringify(b.arguments, (_key, value) =>
|
|
315
|
-
typeof value === "bigint" ? value.toString() : value,
|
|
316
|
-
).length;
|
|
317
|
-
} catch {
|
|
318
|
-
textLen += String(b.arguments).length;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
353
|
+
if (b.arguments !== undefined) textLen += toolArgumentsLength(b.arguments);
|
|
321
354
|
}
|
|
322
355
|
}
|
|
323
356
|
}
|
|
@@ -429,6 +462,25 @@ interface CachedStatusLine {
|
|
|
429
462
|
externalInputs: StatusLineExternalInputs;
|
|
430
463
|
}
|
|
431
464
|
|
|
465
|
+
/** Dot separator of the plain (borderless) layouts; carries no caps. */
|
|
466
|
+
const PLAIN_SEPARATOR: SeparatorDef = { left: "·", right: "·" };
|
|
467
|
+
|
|
468
|
+
/** A separator style's glyphs and cell widths: theme-pure, so measured once per theme. */
|
|
469
|
+
interface SeparatorMetrics {
|
|
470
|
+
themeRef: unknown;
|
|
471
|
+
themeEpoch: number;
|
|
472
|
+
style: StatusLineSeparatorStyle;
|
|
473
|
+
def: SeparatorDef;
|
|
474
|
+
leftSepWidth: number;
|
|
475
|
+
rightSepWidth: number;
|
|
476
|
+
/** Left group's closing cap (`endCaps.right`); 0 without caps. */
|
|
477
|
+
leftCapWidth: number;
|
|
478
|
+
/** Right group's opening cap (`endCaps.left`); 0 without caps. */
|
|
479
|
+
rightCapWidth: number;
|
|
480
|
+
/** Band layout's flush-left soft cap; 0 without caps. */
|
|
481
|
+
bandCapWidth: number;
|
|
482
|
+
}
|
|
483
|
+
|
|
432
484
|
interface ActiveRepoCache {
|
|
433
485
|
projectDir: string;
|
|
434
486
|
activeRepo: ActiveRepoContext | null;
|
|
@@ -588,10 +640,21 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
588
640
|
};
|
|
589
641
|
#statusLineInputRevision = 0;
|
|
590
642
|
#statusLineClockTick = 0;
|
|
643
|
+
/**
|
|
644
|
+
* Earliest wall-clock ms at which a TTL-throttled VCS cache the render path
|
|
645
|
+
* served goes stale (git/jj status, watcher-less branch poll, repository
|
|
646
|
+
* re-probe, PR activity refresh); `Infinity` when none can. The first frame
|
|
647
|
+
* past it expires the render memo so the getters refetch — instead of a
|
|
648
|
+
* fixed polling tick that rebuilt the whole bar every second.
|
|
649
|
+
*/
|
|
650
|
+
#vcsStaleAt = Number.POSITIVE_INFINITY;
|
|
591
651
|
/** Reused probe buffer for {@link #readStatusLineExternalInputs}; handed to the new cache entry on a miss. */
|
|
592
652
|
#externalInputsProbe = new StatusLineExternalInputs();
|
|
593
653
|
/** `adjustHsv` result for the gauge's threshold tint; pure in its source hex. */
|
|
594
654
|
#dimmedAccentMemo: { sourceHex: string; hex: string } | undefined;
|
|
655
|
+
/** Separator widths for the plain and the styled (box/band) layouts; see {@link #separatorMetrics}. */
|
|
656
|
+
#plainSeparatorMetrics: SeparatorMetrics | undefined;
|
|
657
|
+
#styledSeparatorMetrics: SeparatorMetrics | undefined;
|
|
595
658
|
/** Gauge boundary markers; a pure function of the session's compaction settings, model, and window. */
|
|
596
659
|
#compactionBoundariesMemo:
|
|
597
660
|
| {
|
|
@@ -672,7 +735,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
672
735
|
* round-trips because the same {@link StatusLineSession} ref is reused.
|
|
673
736
|
*/
|
|
674
737
|
#activeMeters: WeakMap<TSession, ActiveMeter> = new WeakMap();
|
|
675
|
-
#planModeStatus: { enabled: boolean; paused: boolean } | null = null;
|
|
738
|
+
#planModeStatus: { enabled: boolean; paused: boolean; workflow?: "parallel" | "iterative" | "ultra" } | null = null;
|
|
676
739
|
#loopModeStatus: SegmentContext["loopMode"] = null;
|
|
677
740
|
#goalModeStatus: { enabled: boolean; paused: boolean } | null = null;
|
|
678
741
|
#vibeModeStatus: { enabled: boolean } | null = null;
|
|
@@ -733,6 +796,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
733
796
|
expiryHours?: number;
|
|
734
797
|
expired?: boolean;
|
|
735
798
|
unavailableReason?: string;
|
|
799
|
+
expiring?: StatusResetExpiry;
|
|
736
800
|
};
|
|
737
801
|
} | null = null;
|
|
738
802
|
#cachedUsageContextKey: string | null = null;
|
|
@@ -745,6 +809,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
745
809
|
#latestAppliedUsageRefreshSequence = 0;
|
|
746
810
|
#codexResetSnapshots = new Map<string, CodexResetUsageSnapshot>();
|
|
747
811
|
#onCodexResetFireworks: ((event: CodexResetFireworksEvent) => void) | undefined;
|
|
812
|
+
#onResetExpiryNotice: ((notice: string) => void) | undefined;
|
|
813
|
+
/** Conversations already warned about expiring saved resets: later refreshes stay quiet. */
|
|
814
|
+
#resetExpiryNoticedSessions = new Set<string>();
|
|
748
815
|
// Context-usage memo. The status line redraws on every agent event, so the
|
|
749
816
|
// hot path must not recompute context tokens unless an input changed.
|
|
750
817
|
// `getContextUsage()` anchors on the last assistant's real prompt-token
|
|
@@ -815,27 +882,46 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
815
882
|
#resolveRepository(cache: ActiveRepoCache): VcsRepo | null {
|
|
816
883
|
if (cache.repository) return cache.repository;
|
|
817
884
|
const now = Date.now();
|
|
818
|
-
if (now - cache.repositoryCheckedAt
|
|
819
|
-
|
|
820
|
-
|
|
885
|
+
if (now - cache.repositoryCheckedAt >= WATCHER_FAILURE_POLL_TTL_MS) {
|
|
886
|
+
cache.repository = vcs.repo(cache.effectiveGitCwd);
|
|
887
|
+
cache.repositoryCheckedAt = now;
|
|
888
|
+
}
|
|
889
|
+
// Still absent: re-probe on the bounded interval so a later `git init` shows up.
|
|
890
|
+
if (!cache.repository) this.#noteVcsStaleAt(cache.repositoryCheckedAt + WATCHER_FAILURE_POLL_TTL_MS);
|
|
821
891
|
return cache.repository;
|
|
822
892
|
}
|
|
823
893
|
|
|
824
894
|
#resolveDisplayRepository(cache: ActiveRepoCache): VcsRepo | null {
|
|
825
895
|
if (cache.displayRepository) return cache.displayRepository;
|
|
826
896
|
const now = Date.now();
|
|
827
|
-
if (now - cache.displayRepositoryCheckedAt
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
897
|
+
if (now - cache.displayRepositoryCheckedAt >= WATCHER_FAILURE_POLL_TTL_MS) {
|
|
898
|
+
let display: VcsRepo | null;
|
|
899
|
+
try {
|
|
900
|
+
display = vcs.repoForDisplay(cache.effectiveGitCwd);
|
|
901
|
+
} catch {
|
|
902
|
+
display = null;
|
|
903
|
+
}
|
|
904
|
+
cache.displayRepository = display ?? cache.repository;
|
|
905
|
+
cache.displayRepositoryCheckedAt = now;
|
|
906
|
+
}
|
|
907
|
+
if (!cache.displayRepository) {
|
|
908
|
+
this.#noteVcsStaleAt(cache.displayRepositoryCheckedAt + WATCHER_FAILURE_POLL_TTL_MS);
|
|
833
909
|
}
|
|
834
|
-
cache.displayRepository = display ?? cache.repository;
|
|
835
|
-
cache.displayRepositoryCheckedAt = now;
|
|
836
910
|
return cache.displayRepository;
|
|
837
911
|
}
|
|
838
912
|
|
|
913
|
+
/** Lower {@link #vcsStaleAt} to `at` when a served VCS cache expires sooner. */
|
|
914
|
+
#noteVcsStaleAt(at: number): void {
|
|
915
|
+
if (at < this.#vcsStaleAt) this.#vcsStaleAt = at;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/** Expire the render memo once a served VCS cache has gone stale, so this frame refetches. */
|
|
919
|
+
#expireStaleVcsRender(nowMs: number): void {
|
|
920
|
+
if (nowMs < this.#vcsStaleAt) return;
|
|
921
|
+
this.#vcsStaleAt = Number.POSITIVE_INFINITY;
|
|
922
|
+
this.#invalidateStatusLineRenderCache();
|
|
923
|
+
}
|
|
924
|
+
|
|
839
925
|
/**
|
|
840
926
|
* Re-point the status line at another session (focus proxy). Invalidate: model/context/usage all derive
|
|
841
927
|
* from it. `focusedAgentId` is the focused subagent id while the view is proxied, undefined for main.
|
|
@@ -1025,11 +1111,15 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1025
1111
|
return meter;
|
|
1026
1112
|
}
|
|
1027
1113
|
|
|
1028
|
-
setPlanModeStatus(
|
|
1114
|
+
setPlanModeStatus(
|
|
1115
|
+
status: { enabled: boolean; paused: boolean; workflow?: "parallel" | "iterative" | "ultra" } | undefined,
|
|
1116
|
+
): void {
|
|
1029
1117
|
const next = status ?? null;
|
|
1030
1118
|
if (
|
|
1031
1119
|
this.#planModeStatus === next ||
|
|
1032
|
-
(this.#planModeStatus?.enabled === next?.enabled &&
|
|
1120
|
+
(this.#planModeStatus?.enabled === next?.enabled &&
|
|
1121
|
+
this.#planModeStatus?.paused === next?.paused &&
|
|
1122
|
+
this.#planModeStatus?.workflow === next?.workflow)
|
|
1033
1123
|
) {
|
|
1034
1124
|
return;
|
|
1035
1125
|
}
|
|
@@ -1129,6 +1219,11 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1129
1219
|
this.#onCodexResetFireworks = handler;
|
|
1130
1220
|
}
|
|
1131
1221
|
|
|
1222
|
+
/** Set the callback that presents the first pool-wide saved-reset expiry warning, or clear it with `undefined`. */
|
|
1223
|
+
setResetExpiryNoticeHandler(handler: ((notice: string) => void) | undefined): void {
|
|
1224
|
+
this.#onResetExpiryNotice = handler;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1132
1227
|
setHookStatus(key: string, text: string | undefined): void {
|
|
1133
1228
|
if (text === undefined) {
|
|
1134
1229
|
if (!this.#hookStatuses.delete(key)) return;
|
|
@@ -1198,6 +1293,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1198
1293
|
this.#stopPricingTimer();
|
|
1199
1294
|
this.#clearUsageStartTimer();
|
|
1200
1295
|
this.#onCodexResetFireworks = undefined;
|
|
1296
|
+
this.#onResetExpiryNotice = undefined;
|
|
1201
1297
|
this.#codexResetSnapshots.clear();
|
|
1202
1298
|
this.#retireGitWatcher();
|
|
1203
1299
|
}
|
|
@@ -1236,7 +1332,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1236
1332
|
* a 40ms frame timer so the fade keeps animating after the working loader
|
|
1237
1333
|
* (the usual repaint driver) has stopped.
|
|
1238
1334
|
*/
|
|
1239
|
-
#brandFgAnsi(working: boolean, sessionAccentEnabled: boolean): string {
|
|
1335
|
+
#brandFgAnsi(working: boolean, sessionAccentEnabled: boolean, brandVisible: boolean): string {
|
|
1240
1336
|
const sessionName = sessionAccentEnabled ? this.session.sessionManager?.getSessionName() : undefined;
|
|
1241
1337
|
const idleHex = theme.getColorHex("dim");
|
|
1242
1338
|
const workingHex =
|
|
@@ -1250,7 +1346,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1250
1346
|
startedAt: now,
|
|
1251
1347
|
};
|
|
1252
1348
|
this.#brandWorking = working;
|
|
1253
|
-
|
|
1349
|
+
// Only a visible `pi` segment needs fade frames; other bars stay quiet.
|
|
1350
|
+
if (brandVisible) this.#startBrandFadeTimer();
|
|
1254
1351
|
}
|
|
1255
1352
|
const hex = this.#sampleBrandHex(working ? workingHex : idleHex, now);
|
|
1256
1353
|
return getSessionAccentAnsi(hex) ?? theme.getFgAnsi(working ? "accent" : "dim");
|
|
@@ -1449,7 +1546,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1449
1546
|
taggedRepository[GIT_VIEW] = gitRepository;
|
|
1450
1547
|
}
|
|
1451
1548
|
if (!gitRepository) {
|
|
1452
|
-
if (this.#jjBranchActive
|
|
1549
|
+
if (this.#jjBranchActive) return this.#cachedJjBranch;
|
|
1550
|
+
if (Date.now() - this.#jjBranchLastFetch < JJ_REFRESH_TTL_MS) {
|
|
1551
|
+
this.#noteVcsStaleAt(this.#jjBranchLastFetch + JJ_REFRESH_TTL_MS);
|
|
1453
1552
|
return this.#cachedJjBranch;
|
|
1454
1553
|
}
|
|
1455
1554
|
const request: JjResolveRequest = {
|
|
@@ -1476,6 +1575,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1476
1575
|
if (this.#jjCacheGeneration === generation) this.#jjBranchLastFetch = Date.now();
|
|
1477
1576
|
}
|
|
1478
1577
|
if (this.#jjCacheGeneration !== generation || this.#disposed) return;
|
|
1578
|
+
this.#noteVcsStaleAt(this.#jjBranchLastFetch + JJ_REFRESH_TTL_MS);
|
|
1479
1579
|
const changed = next !== this.#cachedJjBranch;
|
|
1480
1580
|
this.#cachedJjBranch = next;
|
|
1481
1581
|
if (changed) {
|
|
@@ -1489,6 +1589,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1489
1589
|
this.#gitWatcherUnavailable &&
|
|
1490
1590
|
(this.#branchLastFetch === undefined || Date.now() - this.#branchLastFetch >= WATCHER_FAILURE_POLL_TTL_MS);
|
|
1491
1591
|
if (this.#cachedBranch !== undefined && this.#cachedBranchCwd === gitCwd && !fallbackCacheExpired) {
|
|
1592
|
+
if (this.#gitWatcherUnavailable && this.#branchLastFetch !== undefined) {
|
|
1593
|
+
this.#noteVcsStaleAt(this.#branchLastFetch + WATCHER_FAILURE_POLL_TTL_MS);
|
|
1594
|
+
}
|
|
1492
1595
|
return this.#cachedBranch;
|
|
1493
1596
|
}
|
|
1494
1597
|
|
|
@@ -1541,6 +1644,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1541
1644
|
this.#cachedBranchRepoId = repoId;
|
|
1542
1645
|
this.#cachedBranch = next;
|
|
1543
1646
|
this.#branchLastFetch = Date.now();
|
|
1647
|
+
if (this.#gitWatcherUnavailable) {
|
|
1648
|
+
this.#noteVcsStaleAt(this.#branchLastFetch + WATCHER_FAILURE_POLL_TTL_MS);
|
|
1649
|
+
}
|
|
1544
1650
|
if (prev !== next) {
|
|
1545
1651
|
this.#invalidateStatusLineRenderCache();
|
|
1546
1652
|
this.#onBranchChange?.();
|
|
@@ -1561,6 +1667,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1561
1667
|
this.#cachedBranchCwd = gitCwd;
|
|
1562
1668
|
this.#cachedBranchRepoId = gitHeadPath;
|
|
1563
1669
|
this.#branchLastFetch = Date.now();
|
|
1670
|
+
if (this.#gitWatcherUnavailable) this.#noteVcsStaleAt(this.#branchLastFetch + WATCHER_FAILURE_POLL_TTL_MS);
|
|
1564
1671
|
if (!head) {
|
|
1565
1672
|
this.#cachedBranch = null;
|
|
1566
1673
|
return null;
|
|
@@ -1602,7 +1709,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1602
1709
|
const repository = this.#resolveRepository(activeRepoCache);
|
|
1603
1710
|
if (!repository) return null;
|
|
1604
1711
|
if (repository.kind() === "jj") {
|
|
1605
|
-
if (this.#jjStatusActive
|
|
1712
|
+
if (this.#jjStatusActive) return this.#cachedJjStatus;
|
|
1713
|
+
if (Date.now() - this.#jjStatusLastFetch < JJ_REFRESH_TTL_MS) {
|
|
1714
|
+
this.#noteVcsStaleAt(this.#jjStatusLastFetch + JJ_REFRESH_TTL_MS);
|
|
1606
1715
|
return this.#cachedJjStatus;
|
|
1607
1716
|
}
|
|
1608
1717
|
const request: JjResolveRequest = {
|
|
@@ -1624,6 +1733,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1624
1733
|
if (this.#jjCacheGeneration === generation) this.#jjStatusLastFetch = Date.now();
|
|
1625
1734
|
}
|
|
1626
1735
|
if (this.#jjCacheGeneration !== generation || this.#disposed) return;
|
|
1736
|
+
this.#noteVcsStaleAt(this.#jjStatusLastFetch + JJ_REFRESH_TTL_MS);
|
|
1627
1737
|
const prev = this.#cachedJjStatus;
|
|
1628
1738
|
this.#cachedJjStatus = next;
|
|
1629
1739
|
if (JSON.stringify(prev) !== JSON.stringify(next)) {
|
|
@@ -1638,6 +1748,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1638
1748
|
return this.#cachedGitStatusCwd === gitCwd ? this.#cachedGitStatus : null;
|
|
1639
1749
|
}
|
|
1640
1750
|
if (this.#cachedGitStatusCwd === gitCwd && Date.now() - this.#gitStatusLastFetch < GIT_STATUS_TTL_MS) {
|
|
1751
|
+
this.#noteVcsStaleAt(this.#gitStatusLastFetch + GIT_STATUS_TTL_MS);
|
|
1641
1752
|
return this.#cachedGitStatus;
|
|
1642
1753
|
}
|
|
1643
1754
|
|
|
@@ -1657,6 +1768,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1657
1768
|
this.#cachedGitStatusCwd = gitCwd;
|
|
1658
1769
|
this.#gitStatusLastFetch = this.#gitStatusGeneration === generation ? Date.now() : 0;
|
|
1659
1770
|
this.#gitStatusInFlightCwd = undefined;
|
|
1771
|
+
// A zeroed stamp (superseded generation) is already due: refetch next frame.
|
|
1772
|
+
this.#noteVcsStaleAt(this.#gitStatusLastFetch + GIT_STATUS_TTL_MS);
|
|
1660
1773
|
if (!this.#disposed && JSON.stringify(prev) !== JSON.stringify(nextStatus)) {
|
|
1661
1774
|
this.#invalidateStatusLineRenderCache();
|
|
1662
1775
|
this.#onBranchChange?.();
|
|
@@ -1935,6 +2048,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1935
2048
|
this.#invalidateStatusLineRenderCache();
|
|
1936
2049
|
this.#onBranchChange?.();
|
|
1937
2050
|
}
|
|
2051
|
+
this.#noticeResetExpiry(session, reports);
|
|
1938
2052
|
if (!resetSnapshot) return;
|
|
1939
2053
|
const contextKey = this.#formatUsageContextKey(activeProvider, activeIdentity);
|
|
1940
2054
|
const previous = this.#codexResetSnapshots.get(contextKey);
|
|
@@ -1944,6 +2058,33 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1944
2058
|
if (event) this.#onCodexResetFireworks?.(event);
|
|
1945
2059
|
}
|
|
1946
2060
|
|
|
2061
|
+
#noticeResetExpiry(session: TSession, reports: unknown): void {
|
|
2062
|
+
// A focused subagent's view is not a conversation the user started; warn in their own.
|
|
2063
|
+
if (
|
|
2064
|
+
this.#focusedAgentId !== undefined ||
|
|
2065
|
+
!this.#onResetExpiryNotice ||
|
|
2066
|
+
!this.host.resetExpiryNotice ||
|
|
2067
|
+
!Array.isArray(reports)
|
|
2068
|
+
) {
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
const sessionId = session.sessionManager.getSessionId();
|
|
2072
|
+
if (this.#resetExpiryNoticedSessions.has(sessionId)) return;
|
|
2073
|
+
// fetchUsageReports supplies normalized rows; keep only entries shaped like reports.
|
|
2074
|
+
const usageReports = reports.filter(
|
|
2075
|
+
(report): report is UsageReport =>
|
|
2076
|
+
!!report &&
|
|
2077
|
+
typeof report === "object" &&
|
|
2078
|
+
"provider" in report &&
|
|
2079
|
+
"limits" in report &&
|
|
2080
|
+
Array.isArray(report.limits),
|
|
2081
|
+
);
|
|
2082
|
+
const notice = this.host.resetExpiryNotice(usageReports, Date.now());
|
|
2083
|
+
if (!notice) return;
|
|
2084
|
+
this.#resetExpiryNoticedSessions.add(sessionId);
|
|
2085
|
+
this.#onResetExpiryNotice(notice);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
1947
2088
|
#observeLateUsageRefresh(session: TSession, reportsPromise: Promise<unknown>, sequence: number): void {
|
|
1948
2089
|
void reportsPromise
|
|
1949
2090
|
.then(reports => {
|
|
@@ -2050,6 +2191,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2050
2191
|
expiryHours?: number;
|
|
2051
2192
|
expired?: boolean;
|
|
2052
2193
|
unavailableReason?: string;
|
|
2194
|
+
expiring?: StatusResetExpiry;
|
|
2053
2195
|
};
|
|
2054
2196
|
} | null {
|
|
2055
2197
|
if (!Array.isArray(reports)) return null;
|
|
@@ -2167,7 +2309,16 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2167
2309
|
? resetReports.find(report => reportMatchesExactIdentity(report, context.identity))
|
|
2168
2310
|
: undefined;
|
|
2169
2311
|
const resetSummary = summarizeUsageResetCredits(resetReport?.resetCredits, now);
|
|
2170
|
-
const
|
|
2312
|
+
const expiring =
|
|
2313
|
+
resetSummary && resetSummary.bankedCount > 0 && resetReport
|
|
2314
|
+
? this.host.classifyResetExpiry?.(resetReport, now)
|
|
2315
|
+
: undefined;
|
|
2316
|
+
// The highlighted countdown is the warned reset's, not an earlier one that is not worth warning about.
|
|
2317
|
+
const resetExpiryMs = expiring
|
|
2318
|
+
? expiring.expiresAtMs - now
|
|
2319
|
+
: resetSummary?.soonestExpiry
|
|
2320
|
+
? Date.parse(resetSummary.soonestExpiry) - now
|
|
2321
|
+
: undefined;
|
|
2171
2322
|
const resetCredits =
|
|
2172
2323
|
resetSummary && resetSummary.bankedCount > 0
|
|
2173
2324
|
? {
|
|
@@ -2179,6 +2330,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2179
2330
|
: undefined,
|
|
2180
2331
|
expired: resetExpiryMs !== undefined && resetExpiryMs <= 0,
|
|
2181
2332
|
unavailableReason: resetSummary.unavailableReason,
|
|
2333
|
+
expiring,
|
|
2182
2334
|
}
|
|
2183
2335
|
: undefined;
|
|
2184
2336
|
if (!selectedGroup) return resetCredits ? { resetCredits } : null;
|
|
@@ -2387,7 +2539,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2387
2539
|
const gitPr = includePr ? this.#lookupPr(activeRepoCache) : null;
|
|
2388
2540
|
const compactionSpeculation = this.session.compactionSpeculation ?? "idle";
|
|
2389
2541
|
this.#syncSpeculationBlink(compactionSpeculation);
|
|
2390
|
-
const
|
|
2542
|
+
const settings = this.#resolveSettings();
|
|
2543
|
+
const sessionAccentEnabled = settings.sessionAccent !== false;
|
|
2544
|
+
const brandVisible = settings.leftSegments.includes("pi") || settings.rightSegments.includes("pi");
|
|
2391
2545
|
const turnElapsedMs = this.getTurnElapsedMs();
|
|
2392
2546
|
return {
|
|
2393
2547
|
session: this.session,
|
|
@@ -2426,7 +2580,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2426
2580
|
activeMs: this.getActiveMs(),
|
|
2427
2581
|
turnElapsedMs,
|
|
2428
2582
|
now: new Date(nowMs),
|
|
2429
|
-
brandFgAnsi: this.#brandFgAnsi(turnElapsedMs !== null, sessionAccentEnabled),
|
|
2583
|
+
brandFgAnsi: this.#brandFgAnsi(turnElapsedMs !== null, sessionAccentEnabled, brandVisible),
|
|
2430
2584
|
git: {
|
|
2431
2585
|
branch: gitBranch,
|
|
2432
2586
|
status: gitStatus,
|
|
@@ -2655,19 +2809,30 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2655
2809
|
|
|
2656
2810
|
/**
|
|
2657
2811
|
* Smallest wall-clock unit that can change a visible configured segment.
|
|
2658
|
-
* A zero tick keeps truly static bars cached indefinitely;
|
|
2659
|
-
*
|
|
2660
|
-
*
|
|
2812
|
+
* A zero tick keeps truly static bars cached indefinitely; animation, clocks,
|
|
2813
|
+
* and countdowns advance only at their own display cadence. VCS refreshes are
|
|
2814
|
+
* deadline-driven ({@link #expireStaleVcsRender}), not ticked.
|
|
2661
2815
|
*/
|
|
2662
2816
|
#statusLineClock(nowMs: number, effectiveSettings: EffectiveStatusLineSettings): number {
|
|
2663
2817
|
const leftSegments = effectiveSettings.leftSegments;
|
|
2664
2818
|
const rightSegments = effectiveSettings.rightSegments;
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2819
|
+
const turnActive = this.#meter().activeStartedAt !== null;
|
|
2820
|
+
// The `pi` brand is the only spinner-rate animation: its braille spinner
|
|
2821
|
+
// while a turn runs and its color fade across turn edges. While
|
|
2822
|
+
// focus-proxied it shows the viewed agent's id instead.
|
|
2823
|
+
if (
|
|
2824
|
+
(turnActive || this.#brandFade !== null) &&
|
|
2825
|
+
this.#focusedAgentId === undefined &&
|
|
2826
|
+
(leftSegments.includes("pi") || rightSegments.includes("pi"))
|
|
2827
|
+
) {
|
|
2667
2828
|
return Math.floor(nowMs / SPINNER_ADVANCE_MS);
|
|
2668
2829
|
}
|
|
2669
2830
|
const includesTime = leftSegments.includes("time") || rightSegments.includes("time");
|
|
2670
2831
|
if (
|
|
2832
|
+
// A running turn also moves readouts that have no invalidation path of
|
|
2833
|
+
// their own (background-job badge, live and vibe-worker tok/s): refresh
|
|
2834
|
+
// them once a second instead of on the spinner tick.
|
|
2835
|
+
turnActive ||
|
|
2671
2836
|
leftSegments.includes("time_spent") ||
|
|
2672
2837
|
rightSegments.includes("time_spent") ||
|
|
2673
2838
|
(this.#loopModeStatus?.limit?.kind === "duration" &&
|
|
@@ -2679,12 +2844,6 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2679
2844
|
if (includesTime) {
|
|
2680
2845
|
return Math.floor(nowMs / 60_000);
|
|
2681
2846
|
}
|
|
2682
|
-
if (this.#gitEnabled() && (hasGitBackedSegment(leftSegments) || hasGitBackedSegment(rightSegments))) {
|
|
2683
|
-
return Math.floor(nowMs / 1_000);
|
|
2684
|
-
}
|
|
2685
|
-
if (this.#gitEnabled() && (hasPathSegment(leftSegments) || hasPathSegment(rightSegments))) {
|
|
2686
|
-
return Math.floor(nowMs / WATCHER_FAILURE_POLL_TTL_MS);
|
|
2687
|
-
}
|
|
2688
2847
|
return 0;
|
|
2689
2848
|
}
|
|
2690
2849
|
|
|
@@ -2693,6 +2852,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2693
2852
|
const externalInputs = this.#externalInputsProbe;
|
|
2694
2853
|
this.#readStatusLineExternalInputs(externalInputs);
|
|
2695
2854
|
const nowMs = Date.now();
|
|
2855
|
+
this.#expireStaleVcsRender(nowMs);
|
|
2696
2856
|
const clockTick = this.#statusLineClock(nowMs, effectiveSettings);
|
|
2697
2857
|
if (clockTick !== this.#statusLineClockTick) {
|
|
2698
2858
|
this.#statusLineClockTick = clockTick;
|
|
@@ -2728,6 +2888,33 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2728
2888
|
return result;
|
|
2729
2889
|
}
|
|
2730
2890
|
|
|
2891
|
+
/**
|
|
2892
|
+
* Separator glyphs and cell widths for the plain or styled layouts, measured
|
|
2893
|
+
* once per theme (and style) instead of on every rebuild.
|
|
2894
|
+
*/
|
|
2895
|
+
#separatorMetrics(plain: boolean, style: StatusLineSeparatorStyle): SeparatorMetrics {
|
|
2896
|
+
const themeEpoch = getThemeEpoch();
|
|
2897
|
+
const memo = plain ? this.#plainSeparatorMetrics : this.#styledSeparatorMetrics;
|
|
2898
|
+
if (memo && memo.themeRef === theme && memo.themeEpoch === themeEpoch && (plain || memo.style === style)) {
|
|
2899
|
+
return memo;
|
|
2900
|
+
}
|
|
2901
|
+
const def = plain ? PLAIN_SEPARATOR : getSeparator(style, theme);
|
|
2902
|
+
const metrics: SeparatorMetrics = {
|
|
2903
|
+
themeRef: theme,
|
|
2904
|
+
themeEpoch,
|
|
2905
|
+
style,
|
|
2906
|
+
def,
|
|
2907
|
+
leftSepWidth: visibleWidth(def.left),
|
|
2908
|
+
rightSepWidth: visibleWidth(def.right),
|
|
2909
|
+
leftCapWidth: def.endCaps ? visibleWidth(def.endCaps.right) : 0,
|
|
2910
|
+
rightCapWidth: def.endCaps ? visibleWidth(def.endCaps.left) : 0,
|
|
2911
|
+
bandCapWidth: def.endCaps ? visibleWidth(theme.sep.powerlineCapLeft) : 0,
|
|
2912
|
+
};
|
|
2913
|
+
if (plain) this.#plainSeparatorMetrics = metrics;
|
|
2914
|
+
else this.#styledSeparatorMetrics = metrics;
|
|
2915
|
+
return metrics;
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2731
2918
|
/**
|
|
2732
2919
|
* Build the status bar for one of five layouts:
|
|
2733
2920
|
* - `box`: powerline groups joined by the context-reactive gauge line
|
|
@@ -2764,9 +2951,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2764
2951
|
nowMs,
|
|
2765
2952
|
previewTitle,
|
|
2766
2953
|
);
|
|
2767
|
-
const
|
|
2768
|
-
|
|
2769
|
-
: getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
2954
|
+
const separators = this.#separatorMetrics(plain, effectiveSettings.separator ?? "powerline-thin");
|
|
2955
|
+
const separatorDef = separators.def;
|
|
2770
2956
|
|
|
2771
2957
|
// `transparent` reuses the empty-string sentinel (`\x1b[49m`) so the bar
|
|
2772
2958
|
// inherits the terminal's default background, matching custom themes that
|
|
@@ -2837,17 +3023,18 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2837
3023
|
const leftWidths = left.map(part => visibleWidth(part));
|
|
2838
3024
|
const rightWidths = right.map(part => visibleWidth(part));
|
|
2839
3025
|
|
|
2840
|
-
const leftSepWidth =
|
|
2841
|
-
const rightSepWidth =
|
|
3026
|
+
const leftSepWidth = separators.leftSepWidth;
|
|
3027
|
+
const rightSepWidth = separators.rightSepWidth;
|
|
2842
3028
|
// Transparent mode drops powerline caps (they need a bg fill to bridge),
|
|
2843
3029
|
// so the width budget excludes them too.
|
|
2844
|
-
const
|
|
2845
|
-
const
|
|
3030
|
+
const capsVisible = separatorDef.endCaps !== undefined && !transparentBg;
|
|
3031
|
+
const leftCapWidth = capsVisible ? separators.leftCapWidth : 0;
|
|
3032
|
+
const rightCapWidth = capsVisible ? separators.rightCapWidth : 0;
|
|
2846
3033
|
// The band layout opens flush against the terminal edge with a soft cap
|
|
2847
3034
|
// (rust zeta's status band). Like the other caps it needs an opaque
|
|
2848
3035
|
// background to bridge, and only powerline separator styles carry caps.
|
|
2849
|
-
const bandCap = layout === "band" &&
|
|
2850
|
-
const bandCapWidth =
|
|
3036
|
+
const bandCap = layout === "band" && capsVisible ? theme.sep.powerlineCapLeft : "";
|
|
3037
|
+
const bandCapWidth = bandCap ? separators.bandCapWidth : 0;
|
|
2851
3038
|
|
|
2852
3039
|
const groupWidth = (widths: number[], capWidth: number, sepWidth: number): number => {
|
|
2853
3040
|
if (widths.length === 0) return 0;
|
|
@@ -3114,29 +3301,51 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
3114
3301
|
}
|
|
3115
3302
|
const thresholdColor = getSessionAccentAnsi(dimmedAccent.hex) ?? usedColor;
|
|
3116
3303
|
|
|
3304
|
+
// Emit runs, not cells: each stretch of plain rule between markers and
|
|
3305
|
+
// labels is one `repeat`, and color escapes change only at run edges.
|
|
3306
|
+
// Precedence per cell: percent label > threshold > speculation > window label.
|
|
3307
|
+
const percentEnd = percentStart >= 0 ? percentStart + percentLabel.length : -1;
|
|
3308
|
+
const windowEnd = windowStart >= 0 ? windowStart + windowLabel.length : -1;
|
|
3117
3309
|
let out = "\x1b[49m";
|
|
3118
3310
|
let activeColor = "";
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
let
|
|
3122
|
-
|
|
3311
|
+
let i = 0;
|
|
3312
|
+
while (i < gapWidth) {
|
|
3313
|
+
let color: string;
|
|
3314
|
+
let text: string;
|
|
3315
|
+
if (i >= percentStart && i < percentEnd) {
|
|
3316
|
+
const end = Math.min(percentEnd, gapWidth);
|
|
3123
3317
|
color = percentOverflow ? overflowColor : usedColor;
|
|
3124
|
-
|
|
3318
|
+
text = percentLabel.slice(i - percentStart, end - percentStart);
|
|
3319
|
+
i = end;
|
|
3125
3320
|
} else if (i === thresholdIdx) {
|
|
3126
3321
|
color = thresholdColor;
|
|
3127
|
-
|
|
3322
|
+
text = thresholdGlyph;
|
|
3323
|
+
i++;
|
|
3128
3324
|
} else if (i === speculationIdx) {
|
|
3129
3325
|
color = speculationColor;
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3326
|
+
text = speculationGlyph;
|
|
3327
|
+
i++;
|
|
3328
|
+
} else {
|
|
3329
|
+
const inWindow = i >= windowStart && i < windowEnd;
|
|
3330
|
+
let end = Math.min(inWindow ? windowEnd : i < usedCount ? usedCount : gapWidth, gapWidth);
|
|
3331
|
+
if (percentStart > i && percentStart < end) end = percentStart;
|
|
3332
|
+
if (thresholdIdx > i && thresholdIdx < end) end = thresholdIdx;
|
|
3333
|
+
if (speculationIdx > i && speculationIdx < end) end = speculationIdx;
|
|
3334
|
+
if (!inWindow && windowStart > i && windowStart < end) end = windowStart;
|
|
3335
|
+
if (inWindow) {
|
|
3336
|
+
color = thresholdColor;
|
|
3337
|
+
text = windowLabel.slice(i - windowStart, end - windowStart);
|
|
3338
|
+
} else {
|
|
3339
|
+
color = i < usedCount ? usedColor : unusedColor;
|
|
3340
|
+
text = horizontal.repeat(end - i);
|
|
3341
|
+
}
|
|
3342
|
+
i = end;
|
|
3134
3343
|
}
|
|
3135
3344
|
if (color !== activeColor) {
|
|
3136
3345
|
out += color;
|
|
3137
3346
|
activeColor = color;
|
|
3138
3347
|
}
|
|
3139
|
-
out +=
|
|
3348
|
+
out += text;
|
|
3140
3349
|
}
|
|
3141
3350
|
return `${out}\x1b[39m`;
|
|
3142
3351
|
}
|
|
@@ -3323,6 +3532,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
3323
3532
|
const probe = this.#nativeInputsProbe;
|
|
3324
3533
|
this.#readStatusLineExternalInputs(probe);
|
|
3325
3534
|
const nowMs = Date.now();
|
|
3535
|
+
this.#expireStaleVcsRender(nowMs);
|
|
3326
3536
|
const clockTick = this.#nativeClock(nowMs, effectiveSettings);
|
|
3327
3537
|
const memo = this.#nativeMemo;
|
|
3328
3538
|
if (
|
|
@@ -3380,13 +3590,14 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
3380
3590
|
#buildComposerFacts(effectiveSettings: EffectiveStatusLineSettings, nowMs: number): ComposerFacts {
|
|
3381
3591
|
const { leftSegments, rightSegments } = effectiveSettings;
|
|
3382
3592
|
const segments = [...leftSegments, ...rightSegments];
|
|
3383
|
-
// Tern's pane header shows the path
|
|
3593
|
+
// Tern's pane header shows the path from the pane's cwd; the branch sits by the model chip.
|
|
3594
|
+
const gitEnabled = this.#gitEnabled();
|
|
3384
3595
|
const ctx = this.#buildSegmentContext(
|
|
3385
3596
|
0,
|
|
3386
3597
|
effectiveSettings.segmentOptions,
|
|
3387
3598
|
false,
|
|
3388
|
-
|
|
3389
|
-
|
|
3599
|
+
gitEnabled && hasGitSegment(segments),
|
|
3600
|
+
gitEnabled && hasPrSegment(segments),
|
|
3390
3601
|
nowMs,
|
|
3391
3602
|
);
|
|
3392
3603
|
const pr = ctx.git.pr ?? undefined;
|
|
@@ -3404,8 +3615,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
3404
3615
|
if (!view) return;
|
|
3405
3616
|
const props: TspProps<"seg"> = {
|
|
3406
3617
|
role: "zeta.composer.fact",
|
|
3407
|
-
priority:
|
|
3618
|
+
priority:
|
|
3619
|
+
(PINNED_NATIVE_SEGMENTS[id] === true ? PINNED_NATIVE_PRIORITY : 0) +
|
|
3620
|
+
statusSegmentPriority(side, index, ids.length),
|
|
3408
3621
|
};
|
|
3622
|
+
const action = NATIVE_SEGMENT_ACTIONS[id];
|
|
3623
|
+
if (action) props.actions = { click: action };
|
|
3409
3624
|
facts.push(describeSeg(id, props, view, dim));
|
|
3410
3625
|
});
|
|
3411
3626
|
};
|
|
@@ -3509,7 +3724,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
3509
3724
|
/**
|
|
3510
3725
|
* Wall-clock granularity of the native bar. Spinners, turn timers and the
|
|
3511
3726
|
* brand fade are terminal-clocked, so only wall-clock text (time, loop
|
|
3512
|
-
* countdowns)
|
|
3727
|
+
* countdowns) advances it; VCS refreshes are deadline-driven
|
|
3728
|
+
* ({@link #expireStaleVcsRender}).
|
|
3513
3729
|
*/
|
|
3514
3730
|
#nativeClock(nowMs: number, effectiveSettings: EffectiveStatusLineSettings): number {
|
|
3515
3731
|
const segments = [...effectiveSettings.leftSegments, ...effectiveSettings.rightSegments];
|
|
@@ -3521,8 +3737,6 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
3521
3737
|
return Math.floor(nowMs / 1_000);
|
|
3522
3738
|
}
|
|
3523
3739
|
if (includesTime) return Math.floor(nowMs / 60_000);
|
|
3524
|
-
if (this.#gitEnabled() && hasGitBackedSegment(segments)) return Math.floor(nowMs / 1_000);
|
|
3525
|
-
if (this.#gitEnabled() && hasPathSegment(segments)) return Math.floor(nowMs / WATCHER_FAILURE_POLL_TTL_MS);
|
|
3526
3740
|
return 0;
|
|
3527
3741
|
}
|
|
3528
3742
|
|
|
@@ -3614,7 +3828,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
3614
3828
|
}
|
|
3615
3829
|
const showHooks = this.#settings.showHookStatus ?? true;
|
|
3616
3830
|
if (showHooks && this.#sortedHookStatuses.length > 0) {
|
|
3617
|
-
lines.push(...this.#sortedHookStatuses.map(text => truncateToWidth(
|
|
3831
|
+
lines.push(...this.#sortedHookStatuses.map(text => truncateToWidth(sanitizeHookStatusText(text), width)));
|
|
3618
3832
|
}
|
|
3619
3833
|
return lines;
|
|
3620
3834
|
}
|