@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
|
@@ -84,10 +84,12 @@ export interface ProviderCard {
|
|
|
84
84
|
/** Number of represented accounts, including unavailable usage lookups. */
|
|
85
85
|
accounts: number;
|
|
86
86
|
unavailableAccounts: string[];
|
|
87
|
-
/** Window rows
|
|
87
|
+
/** Window rows in provider-declared order (e.g. 5h → weekly → monthly); the fullest CARD_MAX_WINDOWS lead. */
|
|
88
88
|
windows: CardWindowRow[];
|
|
89
|
-
/** True when every account reports no limits (e.g. enterprise plans). */
|
|
89
|
+
/** True when every account reports no limits (e.g. enterprise plans, providers without a quota API). */
|
|
90
90
|
unlimited: boolean;
|
|
91
|
+
/** Distinct report notes across accounts (e.g. why a provider exposes no quota windows). */
|
|
92
|
+
notes: string[];
|
|
91
93
|
/** True when nothing is used anywhere (or there are no limits): collapses to a tick. */
|
|
92
94
|
idle: boolean;
|
|
93
95
|
resetCredits?: {
|
|
@@ -146,8 +148,10 @@ function compactWindowTag(window: NonNullable<UsageLimit["window"]>): string {
|
|
|
146
148
|
* Collapse usage reports into one compact card per provider: limits grouped by
|
|
147
149
|
* quota bucket (label + window), each bucket showing the mean used fraction
|
|
148
150
|
* across accounts (matching the classic report's aggregate "% free") with the
|
|
149
|
-
* most-used account's reset countdown.
|
|
150
|
-
*
|
|
151
|
+
* most-used account's reset countdown. Rows keep the provider's declared window
|
|
152
|
+
* order so 5h → week → month reads the same every time; cards sort by their
|
|
153
|
+
* fullest window so what's burning is on top-left; fully idle providers
|
|
154
|
+
* collapse into a tick.
|
|
151
155
|
*/
|
|
152
156
|
export function buildProviderCards(
|
|
153
157
|
reports: UsageReport[],
|
|
@@ -181,7 +185,7 @@ export function buildProviderCards(
|
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
const
|
|
188
|
+
const declared: CardWindowRow[] = [...buckets.values()].map(bucket => {
|
|
185
189
|
const fractions = bucket.limits
|
|
186
190
|
.map(limit => resolveUsedFraction(limit))
|
|
187
191
|
.filter((value): value is number => value !== undefined);
|
|
@@ -200,7 +204,13 @@ export function buildProviderCards(
|
|
|
200
204
|
usedText: fraction === undefined ? formatAbsoluteOnlyAmount(bucket.limits) : undefined,
|
|
201
205
|
};
|
|
202
206
|
});
|
|
203
|
-
|
|
207
|
+
// Cards render at most CARD_MAX_WINDOWS rows; when a provider declares
|
|
208
|
+
// more, keep the fullest ones visible (still in declared order) so an
|
|
209
|
+
// exhausted bucket never hides behind "+N more".
|
|
210
|
+
const visible = new Set(
|
|
211
|
+
[...declared].sort((a, b) => (b.fraction ?? -1) - (a.fraction ?? -1)).slice(0, CARD_MAX_WINDOWS),
|
|
212
|
+
);
|
|
213
|
+
const windows = [...declared.filter(row => visible.has(row)), ...declared.filter(row => !visible.has(row))];
|
|
204
214
|
// The window tag earns its columns only when sibling rows would otherwise
|
|
205
215
|
// be indistinguishable (e.g. Antigravity's daily vs weekly "Usage (Google)").
|
|
206
216
|
for (const window of windows) {
|
|
@@ -254,6 +264,7 @@ export function buildProviderCards(
|
|
|
254
264
|
unavailableAccounts: unavailable,
|
|
255
265
|
windows,
|
|
256
266
|
unlimited: windows.length === 0 && unavailable.length === 0,
|
|
267
|
+
notes: [...new Set(providerReports.flatMap(report => report.notes ?? []))],
|
|
257
268
|
idle:
|
|
258
269
|
unavailable.length === 0 &&
|
|
259
270
|
!resetCredits &&
|
|
@@ -264,9 +275,12 @@ export function buildProviderCards(
|
|
|
264
275
|
});
|
|
265
276
|
}
|
|
266
277
|
|
|
278
|
+
const worstFraction = new Map(
|
|
279
|
+
cards.map(card => [card, card.windows.reduce((max, window) => Math.max(max, window.fraction ?? -1), -1)]),
|
|
280
|
+
);
|
|
267
281
|
cards.sort((a, b) => {
|
|
268
|
-
const aWorst = a
|
|
269
|
-
const bWorst = b
|
|
282
|
+
const aWorst = worstFraction.get(a)!;
|
|
283
|
+
const bWorst = worstFraction.get(b)!;
|
|
270
284
|
if (aWorst !== bWorst) return bWorst - aWorst;
|
|
271
285
|
return a.name.localeCompare(b.name);
|
|
272
286
|
});
|
|
@@ -440,6 +454,25 @@ function mutedText(content: string, wrap = false): NativeNode {
|
|
|
440
454
|
return text([span(content, "muted")], wrap ? { wrap: "word" } : { truncate: "end" });
|
|
441
455
|
}
|
|
442
456
|
|
|
457
|
+
/**
|
|
458
|
+
* A quiet line under the provider grid naming the providers that get no
|
|
459
|
+
* frame (`Untouched: Kimi Code, Zai`); `title` says why on hover.
|
|
460
|
+
*/
|
|
461
|
+
function footnote(key: string, label: string, cards: readonly ProviderCard[], title?: string): NativeNode {
|
|
462
|
+
const names = cards.map(card => card.name).join(", ");
|
|
463
|
+
return node(
|
|
464
|
+
"text",
|
|
465
|
+
{
|
|
466
|
+
spans: [span(`${label}: `, "dim"), span(names)],
|
|
467
|
+
wrap: "word",
|
|
468
|
+
role: "zeta.usage.footnote",
|
|
469
|
+
...(title ? { title: sanitizeDisplayLine(title) } : {}),
|
|
470
|
+
},
|
|
471
|
+
undefined,
|
|
472
|
+
key,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
443
476
|
/** Stable, human account name for a report in the detail view. */
|
|
444
477
|
function reportAccountLabel(report: UsageReport, limit: UsageLimit | undefined, index: number): string {
|
|
445
478
|
const metadata = report.metadata;
|
|
@@ -463,16 +496,14 @@ function detailWindowLabel(label: string, limit: UsageLimit): string | undefined
|
|
|
463
496
|
return sanitizeDisplayLine(windowLabel);
|
|
464
497
|
}
|
|
465
498
|
|
|
499
|
+
const WHOLE_DOLLARS = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
|
|
500
|
+
const COMPACT_COUNT = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 });
|
|
501
|
+
|
|
466
502
|
/** `$1,234 · 5.6K requests` totals for the activity summary. */
|
|
467
503
|
function formatActivityTotals(layout: HeatmapLayout): string {
|
|
468
504
|
const cost =
|
|
469
|
-
layout.totalCost >= 1
|
|
470
|
-
|
|
471
|
-
: `$${layout.totalCost.toFixed(2)}`;
|
|
472
|
-
const requests = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(
|
|
473
|
-
layout.totalRequests,
|
|
474
|
-
);
|
|
475
|
-
return `${cost} · ${requests} requests`;
|
|
505
|
+
layout.totalCost >= 1 ? `$${WHOLE_DOLLARS.format(layout.totalCost)}` : `$${layout.totalCost.toFixed(2)}`;
|
|
506
|
+
return `${cost} · ${COMPACT_COUNT.format(layout.totalRequests)} requests`;
|
|
476
507
|
}
|
|
477
508
|
|
|
478
509
|
// =============================================================================
|
|
@@ -547,6 +578,8 @@ export class UsageDashboardComponent implements Component {
|
|
|
547
578
|
#activityError: string | null = null;
|
|
548
579
|
#syncing = true;
|
|
549
580
|
#detailCache: { width: number; lines: string[] } | null = null;
|
|
581
|
+
/** ANSI overview rows; rebuilt when the revision or width changes. */
|
|
582
|
+
#overviewCache: { revision: number; width: number; lines: string[] } | null = null;
|
|
550
583
|
#lastViewportRows = 10;
|
|
551
584
|
#closed = false;
|
|
552
585
|
readonly #panel: OverlayPanel;
|
|
@@ -592,6 +625,7 @@ export class UsageDashboardComponent implements Component {
|
|
|
592
625
|
|
|
593
626
|
invalidate(): void {
|
|
594
627
|
this.#detailCache = null;
|
|
628
|
+
this.#overviewCache = null;
|
|
595
629
|
this.#panel.invalidate();
|
|
596
630
|
}
|
|
597
631
|
|
|
@@ -836,15 +870,8 @@ export class UsageDashboardComponent implements Component {
|
|
|
836
870
|
const ramp = this.#heatRamp();
|
|
837
871
|
const reset = "\x1b[39m";
|
|
838
872
|
|
|
839
|
-
const cost =
|
|
840
|
-
layout.totalCost >= 1
|
|
841
|
-
? `$${new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(layout.totalCost)}`
|
|
842
|
-
: `$${layout.totalCost.toFixed(2)}`;
|
|
843
|
-
const requests = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(
|
|
844
|
-
layout.totalRequests,
|
|
845
|
-
);
|
|
846
873
|
summary.push(
|
|
847
|
-
`${theme.bold(theme.fg("accent", "Activity"))} ${theme.fg("dim", `${
|
|
874
|
+
`${theme.bold(theme.fg("accent", "Activity"))} ${theme.fg("dim", `${formatActivityTotals(layout)} · last ${weeks} weeks`)}${this.#syncing ? theme.fg("dim", " · syncing…") : ""}`,
|
|
848
875
|
);
|
|
849
876
|
summary.push("");
|
|
850
877
|
|
|
@@ -876,10 +903,13 @@ export class UsageDashboardComponent implements Component {
|
|
|
876
903
|
// ---------------------------------------------------------------------------
|
|
877
904
|
|
|
878
905
|
#overviewLines(innerWidth: number): string[] {
|
|
906
|
+
const cached = this.#overviewCache;
|
|
907
|
+
if (cached?.revision === this.#revision && cached.width === innerWidth) return cached.lines;
|
|
879
908
|
const lines: string[] = [];
|
|
880
909
|
lines.push(...this.#renderCardsGrid(innerWidth));
|
|
881
910
|
lines.push("");
|
|
882
911
|
lines.push(...this.#renderHeatmap(innerWidth));
|
|
912
|
+
this.#overviewCache = { revision: this.#revision, width: innerWidth, lines };
|
|
883
913
|
return lines;
|
|
884
914
|
}
|
|
885
915
|
|
|
@@ -1012,41 +1042,59 @@ export class UsageDashboardComponent implements Component {
|
|
|
1012
1042
|
const children: NativeChild[] = [];
|
|
1013
1043
|
if (this.#cards.length === 0) {
|
|
1014
1044
|
children.push(
|
|
1015
|
-
node(
|
|
1016
|
-
"text",
|
|
1017
|
-
{ spans: [span("No usage data available.")], role: "zeta.usage.untouched" },
|
|
1018
|
-
undefined,
|
|
1019
|
-
"none",
|
|
1020
|
-
),
|
|
1045
|
+
node("text", { spans: [span("No usage data available.")], role: "zeta.usage.footnote" }, undefined, "none"),
|
|
1021
1046
|
);
|
|
1022
1047
|
} else {
|
|
1023
|
-
//
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1048
|
+
// Only providers with something to show get a frame: the rest are
|
|
1049
|
+
// named in footnotes, whether untouched, unreadable or quota-less.
|
|
1050
|
+
const framed: ProviderCard[] = [];
|
|
1051
|
+
const untouched: ProviderCard[] = [];
|
|
1052
|
+
const unreported: ProviderCard[] = [];
|
|
1053
|
+
const unmetered: ProviderCard[] = [];
|
|
1054
|
+
for (const entry of this.#cards) {
|
|
1055
|
+
const shown =
|
|
1056
|
+
entry.windows.length > 0 || entry.resetCredits !== undefined || entry.daybreakAccounts !== undefined;
|
|
1057
|
+
if (!shown) (entry.unavailableAccounts.length > 0 ? unreported : unmetered).push(entry);
|
|
1058
|
+
else if (entry.idle) untouched.push(entry);
|
|
1059
|
+
else framed.push(entry);
|
|
1060
|
+
}
|
|
1061
|
+
if (framed.length > 0) {
|
|
1027
1062
|
children.push(
|
|
1028
1063
|
node(
|
|
1029
1064
|
"row",
|
|
1030
1065
|
{ wrap: true, gap: "md", role: "zeta.usage.grid" },
|
|
1031
|
-
|
|
1066
|
+
framed.map(entry => this.#describeCard(entry, meter)),
|
|
1032
1067
|
"providers",
|
|
1033
1068
|
),
|
|
1034
1069
|
);
|
|
1035
1070
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1071
|
+
const notes: NativeNode[] = [];
|
|
1072
|
+
if (untouched.length > 0) notes.push(footnote("idle", "Untouched", untouched));
|
|
1073
|
+
if (unreported.length > 0) {
|
|
1074
|
+
const accounts = unreported.flatMap(entry =>
|
|
1075
|
+
entry.unavailableAccounts.map(account => `${entry.name} (${account})`),
|
|
1076
|
+
);
|
|
1077
|
+
notes.push(
|
|
1078
|
+
footnote(
|
|
1079
|
+
"unreported",
|
|
1080
|
+
"No usage data",
|
|
1081
|
+
unreported,
|
|
1082
|
+
`No usage report came back for ${accounts.join(", ")}: the sign-in expired, the request failed, or the plan has no quotas`,
|
|
1047
1083
|
),
|
|
1048
1084
|
);
|
|
1049
1085
|
}
|
|
1086
|
+
if (unmetered.length > 0) {
|
|
1087
|
+
const why = unmetered.flatMap(entry => entry.notes.map(note => `${entry.name}: ${note}`));
|
|
1088
|
+
notes.push(
|
|
1089
|
+
footnote(
|
|
1090
|
+
"unmetered",
|
|
1091
|
+
"No quotas reported",
|
|
1092
|
+
unmetered,
|
|
1093
|
+
why.length > 0 ? why.join(" • ") : "These providers report no quota windows to track",
|
|
1094
|
+
),
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
if (notes.length > 0) children.push(node("col", { gap: "xs" }, notes, "footnotes"));
|
|
1050
1098
|
}
|
|
1051
1099
|
children.push(this.#describeActivity(chart));
|
|
1052
1100
|
return children;
|
|
@@ -1097,10 +1145,10 @@ export class UsageDashboardComponent implements Component {
|
|
|
1097
1145
|
);
|
|
1098
1146
|
}
|
|
1099
1147
|
for (const account of entry.unavailableAccounts) {
|
|
1100
|
-
children.push(mutedText(`${sanitizeDisplayLine(account)}: usage
|
|
1148
|
+
children.push(mutedText(`${sanitizeDisplayLine(account)}: no usage data`, true));
|
|
1101
1149
|
}
|
|
1102
1150
|
if (entry.unlimited) {
|
|
1103
|
-
children.push(mutedText("No
|
|
1151
|
+
children.push(mutedText("No quotas reported"));
|
|
1104
1152
|
} else {
|
|
1105
1153
|
for (const [index, window] of entry.windows.slice(0, CARD_MAX_WINDOWS).entries()) {
|
|
1106
1154
|
const label: TspSpan[] = [span(sanitizeDisplayLine(window.label))];
|
|
@@ -1109,7 +1157,9 @@ export class UsageDashboardComponent implements Component {
|
|
|
1109
1157
|
text(label, { role: "zeta.usage.label", truncate: "middle", title: sanitizeDisplayLine(window.label) }),
|
|
1110
1158
|
];
|
|
1111
1159
|
if (window.fraction === undefined) {
|
|
1112
|
-
cells.push(
|
|
1160
|
+
cells.push(
|
|
1161
|
+
text([span(window.usedText ?? "No data", "muted")], { role: "zeta.usage.amount", truncate: "end" }),
|
|
1162
|
+
);
|
|
1113
1163
|
} else {
|
|
1114
1164
|
const token =
|
|
1115
1165
|
window.status === "exhausted" ? "error" : window.status === "warning" ? "warning" : undefined;
|
|
@@ -1226,7 +1276,7 @@ export class UsageDashboardComponent implements Component {
|
|
|
1226
1276
|
const children: NativeChild[] = [];
|
|
1227
1277
|
for (const account of unavailable) {
|
|
1228
1278
|
if (account.provider !== entry.provider) continue;
|
|
1229
|
-
children.push(mutedText(`${sanitizeDisplayLine(account.label)}: usage
|
|
1279
|
+
children.push(mutedText(`${sanitizeDisplayLine(account.label)}: no usage data`, true));
|
|
1230
1280
|
}
|
|
1231
1281
|
|
|
1232
1282
|
const facts: { k: TspText; v: TspText }[] = [];
|
|
@@ -1318,7 +1368,7 @@ export class UsageDashboardComponent implements Component {
|
|
|
1318
1368
|
children.push(node("table", { cols, rows }, undefined, "limits"));
|
|
1319
1369
|
for (const note of new Set(limitNotes)) children.push(mutedText(note, true));
|
|
1320
1370
|
} else if (unavailable.every(account => account.provider !== entry.provider)) {
|
|
1321
|
-
children.push(mutedText("No
|
|
1371
|
+
children.push(mutedText("No quotas reported"));
|
|
1322
1372
|
}
|
|
1323
1373
|
sections.push(
|
|
1324
1374
|
node(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UsageLimit, UsageReport, UsageResetCredits } from "@linxiraos/pi-ai";
|
|
1
|
+
import type { UsageLimit, UsageReport, UsageResetCreditDetail, UsageResetCredits } from "@linxiraos/pi-ai";
|
|
2
2
|
|
|
3
3
|
/** Include the usage tier in a limit title unless its label already names it. */
|
|
4
4
|
export function formatLimitTitle(limit: UsageLimit): string {
|
|
@@ -51,6 +51,13 @@ export interface UsageResetSummary {
|
|
|
51
51
|
unavailableReason?: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/** Expiry (epoch ms) of a credit that still banks a reset, whether or not it can be spent now. */
|
|
55
|
+
export function bankedResetCreditExpiryMs(credit: UsageResetCreditDetail): number | undefined {
|
|
56
|
+
if (!credit.expiresAt || credit.remainingCount === 0 || credit.status === "redeemed") return undefined;
|
|
57
|
+
const expiryMs = Date.parse(credit.expiresAt);
|
|
58
|
+
return Number.isFinite(expiryMs) ? expiryMs : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
/**
|
|
55
62
|
* Normalize old count-only and current provider reset metadata for display.
|
|
56
63
|
* `availableCount` is banked inventory; `redeemableCount` is the subset that
|
|
@@ -68,9 +75,8 @@ export function summarizeUsageResetCredits(
|
|
|
68
75
|
let latestExpired: string | undefined;
|
|
69
76
|
let latestExpiredMs = Number.NEGATIVE_INFINITY;
|
|
70
77
|
for (const credit of reset.credits ?? []) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!Number.isFinite(expiryMs)) continue;
|
|
78
|
+
const expiryMs = bankedResetCreditExpiryMs(credit);
|
|
79
|
+
if (expiryMs === undefined) continue;
|
|
74
80
|
if (expiryMs > nowMs && expiryMs < soonestExpiryMs) {
|
|
75
81
|
soonestExpiryMs = expiryMs;
|
|
76
82
|
soonestExpiry = credit.expiresAt;
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
visibleWidth,
|
|
13
13
|
} from "../index";
|
|
14
14
|
import { fileHyperlink } from "../render/hyperlink";
|
|
15
|
-
import {
|
|
15
|
+
import { nativeImageNode } from "../native/blobs";
|
|
16
16
|
import { node, row, span } from "../native/describe";
|
|
17
17
|
import { plainText } from "../native/spans";
|
|
18
18
|
import type { DescribeContext, NativeNode } from "../native/node";
|
|
@@ -33,12 +33,9 @@ const RESET_FG = "\x1b[39m";
|
|
|
33
33
|
* (pastes are usually re-encoded JPEG/WebP) convert before transmit — the same pipeline
|
|
34
34
|
* the transcript uses. `null` = conversion in flight or failed. */
|
|
35
35
|
const kImagePng = Symbol("zeta.imagePng");
|
|
36
|
-
/** Content address of the draft image's decoded bytes, registered once for TSP `image` nodes. */
|
|
37
|
-
const kImageBlob = Symbol("zeta.imageBlob");
|
|
38
36
|
|
|
39
37
|
interface ImageContentWithPng extends ImageContent {
|
|
40
38
|
[kImagePng]?: ImageContent | null;
|
|
41
|
-
[kImageBlob]?: string;
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
/**
|
|
@@ -57,7 +54,9 @@ export class AttachmentChipsBand implements Component {
|
|
|
57
54
|
private readonly requestRender: () => void,
|
|
58
55
|
) {}
|
|
59
56
|
|
|
60
|
-
#native:
|
|
57
|
+
#native:
|
|
58
|
+
| { chips: readonly ComposerChipDescriptor[]; node: NativeNode; images: ReadonlyMap<ImageContent, NativeNode> }
|
|
59
|
+
| undefined;
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
62
|
* A wrapping `row` of chip `card`s (`zeta.composer.chip`) titled with the
|
|
@@ -69,6 +68,7 @@ export class AttachmentChipsBand implements Component {
|
|
|
69
68
|
const chips = this.editor.composerChips();
|
|
70
69
|
if (this.#native?.chips === chips) return this.#native.node;
|
|
71
70
|
const cards: NativeNode[] = [];
|
|
71
|
+
const images = new Map<ImageContent, NativeNode>();
|
|
72
72
|
for (const chip of chips) {
|
|
73
73
|
const icon = theme.symbol(
|
|
74
74
|
chip.kind === "paste" ? "chip.paste" : chip.kind === "video" ? "chip.video" : "chip.image",
|
|
@@ -86,11 +86,14 @@ export class AttachmentChipsBand implements Component {
|
|
|
86
86
|
} else {
|
|
87
87
|
const dims = this.#imageDims(chip.image);
|
|
88
88
|
caption = dims ? `${dims.width}x${dims.height}` : "";
|
|
89
|
-
const image = chip.image
|
|
90
|
-
const
|
|
91
|
-
|
|
89
|
+
const image = chip.image;
|
|
90
|
+
const native =
|
|
91
|
+
images.get(image) ??
|
|
92
|
+
this.#native?.images.get(image) ??
|
|
93
|
+
nativeImageNode(Buffer.from(image.data, "base64"), image.mimeType);
|
|
94
|
+
images.set(image, native);
|
|
92
95
|
content = node("image", {
|
|
93
|
-
|
|
96
|
+
...native.p,
|
|
94
97
|
alt: title,
|
|
95
98
|
w: dims?.width,
|
|
96
99
|
h: dims?.height,
|
|
@@ -106,7 +109,7 @@ export class AttachmentChipsBand implements Component {
|
|
|
106
109
|
);
|
|
107
110
|
}
|
|
108
111
|
const described = row(cards, { gap: "sm", wrap: true, role: "zeta.composer.chips", hidden: cards.length === 0 });
|
|
109
|
-
this.#native = { chips, node: described };
|
|
112
|
+
this.#native = { chips, node: described, images };
|
|
110
113
|
return described;
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -249,7 +252,8 @@ export class AttachmentChipsBand implements Component {
|
|
|
249
252
|
|
|
250
253
|
/** Leading 4 rows x 12 cols of the pasted text, muted. */
|
|
251
254
|
#textInterior(entry: TextAttachment): string[] {
|
|
252
|
-
|
|
255
|
+
// Only the leading rows are drawn; don't split a multi-KB paste every frame.
|
|
256
|
+
const lines = entry.content.split("\n", INNER_ROWS);
|
|
253
257
|
const rows: string[] = [];
|
|
254
258
|
for (let r = 0; r < INNER_ROWS; r++) {
|
|
255
259
|
const cut = truncateToWidth(replaceTabs(lines[r] ?? ""), INNER_COLS);
|
|
@@ -165,6 +165,9 @@ export const COMPOSER_TOKEN_REGEX = new RegExp(
|
|
|
165
165
|
"gu",
|
|
166
166
|
);
|
|
167
167
|
|
|
168
|
+
/** Last compiled {@link referencedAttachments} scanner, keyed by its source (labels and glyphs rarely change). */
|
|
169
|
+
let cachedScanner: { source: string; scanner: RegExp } | undefined;
|
|
170
|
+
|
|
168
171
|
/**
|
|
169
172
|
* Attachment indices referenced by a composer buffer, per kind. Image/video count compact chips
|
|
170
173
|
* and expanded markers; paste counts compact chips only, since `[Paste #N]` markers number the
|
|
@@ -182,10 +185,9 @@ export function referencedAttachments(
|
|
|
182
185
|
const icons = [...kindByIcon.keys()].sort((a, b) => b.length - a.length).map(glyphSource);
|
|
183
186
|
const labels = [...recorded.keys()].sort((a, b) => b.length - a.length).map(label => RegExp.escape(label));
|
|
184
187
|
const recordedSource = labels.length > 0 ? labels.join("|") : "(?!)";
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
);
|
|
188
|
+
const source = `${PLACEHOLDER_REGEX.source}|(${recordedSource})(?!\\d)|(${icons.join("|")}) #([1-9]\\d*)`;
|
|
189
|
+
if (cachedScanner?.source !== source) cachedScanner = { source, scanner: new RegExp(source, "gu") };
|
|
190
|
+
const { scanner } = cachedScanner;
|
|
189
191
|
|
|
190
192
|
const refs: Record<ChipKind, Set<number>> = { image: new Set(), video: new Set(), paste: new Set() };
|
|
191
193
|
for (const match of text.matchAll(scanner)) {
|
package/src/prompt/composer.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type TUIOptions,
|
|
16
16
|
type ViewportSize,
|
|
17
17
|
} from "../tui";
|
|
18
|
-
import {
|
|
18
|
+
import { wrapLiteralLine } from "../utils";
|
|
19
19
|
import type { NativeChild, NativeSurface, NativeSurfaceProvider } from "../native/node";
|
|
20
20
|
import { sameItems } from "../native/memo";
|
|
21
21
|
import { postmortem } from "@linxiraos/pi-utils";
|
|
@@ -82,7 +82,7 @@ export interface ComposerStatusCache {
|
|
|
82
82
|
/** Optional dependencies and initial state for a standalone composer. */
|
|
83
83
|
export interface ComposerOptions {
|
|
84
84
|
readonly terminal?: Terminal;
|
|
85
|
-
/** Extra TUI construction options (render scheduler injection for tests and `
|
|
85
|
+
/** Extra TUI construction options (render scheduler injection for tests and `zetacode render`). */
|
|
86
86
|
readonly tuiOptions?: TUIOptions;
|
|
87
87
|
readonly preferences?: Partial<ComposerPreferences>;
|
|
88
88
|
readonly welcome?: ComposerWelcomeUpdate;
|
|
@@ -812,20 +812,8 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
812
812
|
if (!lines) return [];
|
|
813
813
|
if (isInsideTerminalMultiplexer()) return lines.slice(start);
|
|
814
814
|
const reflowed: string[] = [];
|
|
815
|
-
const columns = Math.max(1, width);
|
|
816
815
|
for (let index = start; index < lines.length; index++) {
|
|
817
|
-
|
|
818
|
-
const lineWidth = visibleWidth(line);
|
|
819
|
-
if (lineWidth === 0) {
|
|
820
|
-
reflowed.push("");
|
|
821
|
-
continue;
|
|
822
|
-
}
|
|
823
|
-
for (let column = 0; column < lineWidth;) {
|
|
824
|
-
let slice = sliceWithWidth(line, column, columns, true);
|
|
825
|
-
if (slice.width === 0) slice = sliceWithWidth(line, column, columns);
|
|
826
|
-
reflowed.push(slice.text);
|
|
827
|
-
column += Math.max(1, slice.width);
|
|
828
|
-
}
|
|
816
|
+
reflowed.push(...wrapLiteralLine(lines[index]!, width));
|
|
829
817
|
}
|
|
830
818
|
return reflowed;
|
|
831
819
|
}
|
|
@@ -921,6 +909,7 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
921
909
|
|
|
922
910
|
/** Update the canonical editor reference after InteractiveMode remounts a custom editor. */
|
|
923
911
|
setEditor(editor: CustomEditor): void {
|
|
912
|
+
this.ui.replaceHeldFocus(this.#editor, editor);
|
|
924
913
|
this.#editor = editor;
|
|
925
914
|
}
|
|
926
915
|
|
|
@@ -20,7 +20,6 @@ import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node"
|
|
|
20
20
|
import type { ComposerFacts, ComposerFactsSource } from "../status-line/types";
|
|
21
21
|
import { allowsModelMentions, allowsSkillTokens, SKILL_TOKEN_RE } from "./skill-tokens";
|
|
22
22
|
import { expandModelMentionTags, MODEL_MENTION_RE, modelMentionToken } from "./model-mention-syntax";
|
|
23
|
-
import { imageAttachmentSource } from "./image-source";
|
|
24
23
|
import { isVideoPath } from "./video";
|
|
25
24
|
import {
|
|
26
25
|
attachmentSgr,
|
|
@@ -41,7 +40,7 @@ import {
|
|
|
41
40
|
} from "./composer-attachments";
|
|
42
41
|
import { type MacOSSpellingFeatures, MacOSSpellingProvider } from "./macos-spelling";
|
|
43
42
|
import { hasMagicKeyword, highlightMagicKeywords, magicKeywordRanges } from "./magic-keywords";
|
|
44
|
-
import type { TspEditorDecoration } from "@linxiraos/pi-wire";
|
|
43
|
+
import type { TspEditorDecoration, TspText } from "@linxiraos/pi-wire";
|
|
45
44
|
import { isNativeRendering } from "../native/state";
|
|
46
45
|
import { isQueuedMessageList, parseQueueShorthand, QUEUE_LIST_MARKER_RE } from "./queue-input";
|
|
47
46
|
import { type WordCompletionMethod, WordCompletionProvider } from "./word-completion";
|
|
@@ -50,8 +49,8 @@ import { fgOrPlain, theme } from "../theme/theme";
|
|
|
50
49
|
/** A shell-mode draft's sigil (`!`, `!!`, `$`, `$$`) with its surrounding blanks; the mode chip stands in for it natively. */
|
|
51
50
|
const SHELL_SIGIL_RE = /^\s*(?:!!?|\$\$?)[ \t]?/;
|
|
52
51
|
|
|
53
|
-
/** The
|
|
54
|
-
const NATIVE_COMPOSER_PLACEHOLDER = "
|
|
52
|
+
/** The untitled session's TSP placeholder; the rotating ANSI hints (thinking effort, …) move into control tooltips. */
|
|
53
|
+
const NATIVE_COMPOSER_PLACEHOLDER = "What are we cooking?";
|
|
55
54
|
|
|
56
55
|
/** Live composer state the TSP layout shows; the interactive host wires {@link CustomEditor.composerState}. */
|
|
57
56
|
export interface ComposerNativeState {
|
|
@@ -67,6 +66,8 @@ export interface ComposerNativeState {
|
|
|
67
66
|
readonly running: boolean;
|
|
68
67
|
/** The viewed subagent's lineage, outermost first and the viewed agent last; undefined on the main session. */
|
|
69
68
|
readonly viewing?: readonly string[];
|
|
69
|
+
/** The session's title, quoted in italics as the TSP placeholder; undefined until it has one. */
|
|
70
|
+
readonly title?: string;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/** Action code prefix of the viewing header's links: `focus:<agent id>`, {@link MAIN_AGENT_ID} for the main session. */
|
|
@@ -220,7 +221,7 @@ function normalizePastedPath(path: string): string {
|
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
|
-
return unquoted.replace(SHELL_ESCAPED_PATH_CHAR_REGEX, "$1");
|
|
224
|
+
return (unquoted.startsWith("\\~/") ? unquoted.slice(1) : unquoted).replace(SHELL_ESCAPED_PATH_CHAR_REGEX, "$1");
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
function isExplicitPastedPath(path: string): boolean {
|
|
@@ -408,7 +409,7 @@ export function extractImagePathFromText(text: string): string | undefined {
|
|
|
408
409
|
|
|
409
410
|
/**
|
|
410
411
|
* Resolve the {@link EditorTheme} from a `CustomEditor`/`Editor` constructor
|
|
411
|
-
* argument list, tolerating both the
|
|
412
|
+
* argument list, tolerating both the omp `(theme)` and upstream-pi
|
|
412
413
|
* `(tui, theme, keybindings)` conventions (see {@link CustomEditor}'s
|
|
413
414
|
* constructor). A real `EditorTheme` is identified structurally — it exposes a
|
|
414
415
|
* `borderColor` function and a `symbols` object — so a `TUI` passed in the first
|
|
@@ -478,13 +479,13 @@ export class CustomEditor extends Editor {
|
|
|
478
479
|
chips: ComposerChipDescriptor[];
|
|
479
480
|
}
|
|
480
481
|
| undefined;
|
|
481
|
-
/** Host-wired producer of per-image
|
|
482
|
-
* chip tokens for restored drafts (esc-esc, `/tree`, branch). */
|
|
482
|
+
/** Host-wired producer of per-image chip targets (a file on disk, or a session blob copy);
|
|
483
|
+
* drives clickable chip tokens for restored drafts (esc-esc, `/tree`, branch). */
|
|
483
484
|
draftImageLinkMaterializer?: (images: readonly ImageContent[]) => Promise<(string | undefined)[] | undefined>;
|
|
484
485
|
|
|
485
486
|
/**
|
|
486
487
|
* The host {@link TUI}, captured when a plugin constructs this editor through
|
|
487
|
-
* the upstream-pi `(tui, theme, keybindings)` convention. Undefined for
|
|
488
|
+
* the upstream-pi `(tui, theme, keybindings)` convention. Undefined for omp's
|
|
488
489
|
* own `new CustomEditor(theme)` callers (they drive repaints through the
|
|
489
490
|
* interactive-mode wiring instead). Plugins that call `this.tui.requestRender()`
|
|
490
491
|
* in their overrides read it here (issue #4766).
|
|
@@ -492,7 +493,7 @@ export class CustomEditor extends Editor {
|
|
|
492
493
|
tui?: TUI;
|
|
493
494
|
|
|
494
495
|
/**
|
|
495
|
-
* Accept both the
|
|
496
|
+
* Accept both the omp constructor convention — `new CustomEditor(theme)` —
|
|
496
497
|
* and the upstream-pi `Editor` convention — `new Editor(tui, theme, keybindings)`
|
|
497
498
|
* — that {@link ExtensionUIContext.setEditorComponent}'s factory contract
|
|
498
499
|
* advertises `(tui, theme, keybindings)`. Plugins written against upstream pi
|
|
@@ -789,7 +790,7 @@ export class CustomEditor extends Editor {
|
|
|
789
790
|
if (!materialize || images.length === 0) return;
|
|
790
791
|
const links = await materialize(images);
|
|
791
792
|
if (!links || this.pendingImages !== images) return;
|
|
792
|
-
this.pendingImageLinks =
|
|
793
|
+
this.pendingImageLinks = links;
|
|
793
794
|
this.imageLinks = this.pendingImageLinks;
|
|
794
795
|
this.#requestShimmerRepaint?.();
|
|
795
796
|
}
|
|
@@ -798,10 +799,21 @@ export class CustomEditor extends Editor {
|
|
|
798
799
|
* indivisible: a stray backspace deletes the whole token instead of corrupting it. */
|
|
799
800
|
override atomicTokenPattern = COMPOSER_TOKEN_REGEX;
|
|
800
801
|
|
|
802
|
+
/** Atom-table revision and pattern the composer token matcher was last built for. */
|
|
803
|
+
#tokenPatternAtomsRevision = -1;
|
|
804
|
+
#tokenPattern: RegExp | undefined;
|
|
805
|
+
|
|
801
806
|
#syncComposerTokenPattern(): void {
|
|
802
|
-
|
|
807
|
+
// Rebuild only when the atom table changed or someone replaced the pattern since the last sync.
|
|
808
|
+
if (this.#tokenPatternAtomsRevision === this.atomsRevision && this.#tokenPattern === this.atomicTokenPattern) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const labels: string[] = [];
|
|
812
|
+
for (const [label, expansion] of this.atoms) if (expansion.startsWith("^")) labels.push(label);
|
|
803
813
|
const next = composerTokenRegex(labels);
|
|
804
814
|
if (next.source !== this.atomicTokenPattern.source) this.atomicTokenPattern = next;
|
|
815
|
+
this.#tokenPatternAtomsRevision = this.atomsRevision;
|
|
816
|
+
this.#tokenPattern = this.atomicTokenPattern;
|
|
805
817
|
}
|
|
806
818
|
|
|
807
819
|
/** Magic-keyword shimmer cadence — drives one editor repaint every 70 ms while
|
|
@@ -819,27 +831,35 @@ export class CustomEditor extends Editor {
|
|
|
819
831
|
* timer to request the next animation frame. Undefined when nobody is
|
|
820
832
|
* listening (tests, headless callers); the timer chain still self-cleans. */
|
|
821
833
|
#requestShimmerRepaint: (() => void) | undefined;
|
|
822
|
-
|
|
834
|
+
/** Text revision the per-revision decoration inputs below were computed for. */
|
|
835
|
+
#decorationRevision = -1;
|
|
836
|
+
#decorationText = "";
|
|
823
837
|
#decorationLines: readonly string[] = [""];
|
|
824
838
|
#queueShorthandActive = false;
|
|
825
839
|
#queueListActive = false;
|
|
826
840
|
|
|
827
841
|
/** Decorate magic keywords, attachments, and the queue-composer header/list markers.
|
|
828
842
|
* Queue shorthand reserves its first logical line as a dim `Queueing` label; sequential
|
|
829
|
-
* item markers use the accent color so separate follow-ups remain visible while composing.
|
|
843
|
+
* item markers use the accent color so separate follow-ups remain visible while composing.
|
|
844
|
+
* Called once per layout segment, so whole-buffer inputs are computed once per text revision. */
|
|
830
845
|
override decorateText = (text: string, context: EditorTextDecorationContext): string => {
|
|
831
846
|
this.#syncComposerTokenPattern();
|
|
832
|
-
|
|
847
|
+
if (this.#decorationRevision !== this.textRevision) {
|
|
848
|
+
this.#decorationRevision = this.textRevision;
|
|
849
|
+
const editorText = this.getText();
|
|
850
|
+
if (this.#decorationText !== editorText) {
|
|
851
|
+
this.#decorationText = editorText;
|
|
852
|
+
this.#decorationLines = this.getLines();
|
|
853
|
+
const queueBody = parseQueueShorthand(editorText);
|
|
854
|
+
this.#queueShorthandActive = queueBody !== undefined;
|
|
855
|
+
this.#queueListActive = queueBody !== undefined && isQueuedMessageList(queueBody);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
// One string instance per revision: whole-buffer memos downstream hit on identity.
|
|
859
|
+
const editorText = this.#decorationText;
|
|
833
860
|
const animated = this.focused && this.#shimmerEnabled() && hasMagicKeyword(editorText);
|
|
834
861
|
const phase = animated ? (Date.now() % CustomEditor.SHIMMER_PERIOD_MS) / CustomEditor.SHIMMER_PERIOD_MS : 0;
|
|
835
862
|
if (animated) this.#scheduleShimmerFrame();
|
|
836
|
-
if (this.#queueDecorationText !== editorText) {
|
|
837
|
-
this.#queueDecorationText = editorText;
|
|
838
|
-
this.#decorationLines = this.getLines();
|
|
839
|
-
const queueBody = parseQueueShorthand(editorText);
|
|
840
|
-
this.#queueShorthandActive = queueBody !== undefined;
|
|
841
|
-
this.#queueListActive = queueBody !== undefined && isQueuedMessageList(queueBody);
|
|
842
|
-
}
|
|
843
863
|
let sourceSearchOffset = 0;
|
|
844
864
|
const locateSource = (value: string): number => {
|
|
845
865
|
const offset = text.indexOf(value, sourceSearchOffset);
|
|
@@ -1018,10 +1038,18 @@ export class CustomEditor extends Editor {
|
|
|
1018
1038
|
protected override get nativeSendable(): boolean {
|
|
1019
1039
|
return this.onSubmit !== undefined && !this.disableSubmit;
|
|
1020
1040
|
}
|
|
1021
|
-
/**
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1041
|
+
/** The quoted-title placeholder, kept while the title stands so the editor node stays reused. */
|
|
1042
|
+
#titlePlaceholder: { title: string; text: TspText } | undefined;
|
|
1043
|
+
/** Viewing a subagent, the draft goes to it: the placeholder names it; else the session's title, quoted in italics. */
|
|
1044
|
+
override describePlaceholder = (): TspText => {
|
|
1045
|
+
const { viewing, title } = this.composerState();
|
|
1046
|
+
const agent = viewing?.at(-1);
|
|
1047
|
+
if (agent !== undefined) return `Message ${agent}`;
|
|
1048
|
+
if (!title) return NATIVE_COMPOSER_PLACEHOLDER;
|
|
1049
|
+
if (this.#titlePlaceholder?.title !== title) {
|
|
1050
|
+
this.#titlePlaceholder = { title, text: [{ t: `“${title}”`, s: "em" }] };
|
|
1051
|
+
}
|
|
1052
|
+
return this.#titlePlaceholder.text;
|
|
1025
1053
|
};
|
|
1026
1054
|
/** A shell-mode draft highlights as its language. */
|
|
1027
1055
|
override describeLanguage = (): string | undefined => this.composerState().shell?.kind;
|