@pellux/goodvibes-tui 2.0.20 → 2.1.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 +70 -0
- package/README.md +16 -16
- package/docs/README.md +4 -4
- package/docs/foundation-artifacts/operator-contract.json +59 -4
- package/package.json +16 -6
- package/src/cli/management.ts +5 -2
- package/src/cli/tui-startup.ts +34 -39
- package/src/core/agent-view-render.ts +157 -0
- package/src/core/approval-alert.ts +49 -11
- package/src/core/bookmark-navigation.ts +7 -1
- package/src/core/budget-breach-notifier.ts +27 -8
- package/src/core/compaction-receipt.ts +1 -1
- package/src/core/context-usage.ts +1 -1
- package/src/core/conversation-compaction-render.ts +3 -3
- package/src/core/conversation-display-only.ts +86 -0
- package/src/core/conversation-event-navigation.ts +27 -0
- package/src/core/conversation-line-cache.ts +216 -447
- package/src/core/conversation-render-context.ts +20 -27
- package/src/core/conversation-rendering.ts +157 -575
- package/src/core/conversation-splash-state.ts +6 -1
- package/src/core/conversation-types.ts +35 -0
- package/src/core/conversation-usage.ts +12 -0
- package/src/core/conversation.ts +130 -131
- package/src/core/long-task-notifier.ts +100 -31
- package/src/core/notices.ts +75 -0
- package/src/core/pairing-tailscale-gateway.ts +1 -1
- package/src/core/process-output-log.ts +143 -0
- package/src/core/rewind-receipt.ts +1 -1
- package/src/core/session-focus.ts +190 -0
- package/src/core/session-resume-core.ts +23 -81
- package/src/core/stream-event-wiring.ts +31 -5
- package/src/core/system-message-noise.ts +2 -2
- package/src/core/system-message-router.ts +16 -18
- package/src/core/turn-event-wiring.ts +272 -33
- package/src/core/work-tree-focus.ts +290 -0
- package/src/core/work-tree-fold-store.ts +143 -0
- package/src/core/work-tree-model.ts +799 -0
- package/src/core/work-tree-render.ts +384 -0
- package/src/core/work-tree-sources.ts +184 -0
- package/src/core/work-tree-wiring.ts +176 -0
- package/src/core/workstream-notification.ts +36 -6
- package/src/core/wrfc-notice-titles.ts +37 -0
- package/src/input/agents-modal-text.ts +119 -0
- package/src/input/agents-modal.ts +699 -0
- package/src/input/bookmark-modal.ts +33 -9
- package/src/input/changes-git.ts +251 -0
- package/src/input/changes-modal.ts +629 -0
- package/src/input/changes-model.ts +125 -0
- package/src/input/command-palette-types.ts +41 -0
- package/src/input/command-palette.ts +272 -0
- package/src/input/command-registry.ts +77 -35
- package/src/input/commands/acp-runtime.ts +19 -9
- package/src/input/commands/channel-runtime.ts +2 -2
- package/src/input/commands/ci-runtime.ts +1 -1
- package/src/input/commands/codebase-runtime.ts +1 -1
- package/src/input/commands/config.ts +7 -1
- package/src/input/commands/context-window.ts +25 -19
- package/src/input/commands/control-room-runtime.ts +18 -18
- package/src/input/commands/conversation-runtime.ts +0 -1
- package/src/input/commands/cost-runtime.ts +29 -20
- package/src/input/commands/diff-runtime.ts +35 -181
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/experience-runtime.ts +3 -7
- package/src/input/commands/fresh-conversation.ts +86 -0
- package/src/input/commands/git-runtime.ts +5 -22
- package/src/input/commands/graph-runtime.ts +1 -1
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +8 -9
- package/src/input/commands/hooks-runtime.ts +2 -2
- package/src/input/commands/hosted-runtime.ts +3 -3
- package/src/input/commands/incident-runtime.ts +3 -3
- package/src/input/commands/intelligence-runtime.ts +6 -9
- package/src/input/commands/knowledge.ts +2 -2
- package/src/input/commands/legacy-panel-command.ts +57 -0
- package/src/input/commands/local-auth-runtime.ts +8 -9
- package/src/input/commands/local-provider-runtime.ts +7 -4
- package/src/input/commands/local-runtime.ts +12 -15
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/local-setup.ts +13 -19
- package/src/input/commands/memory.ts +1 -1
- package/src/input/commands/notify-runtime.ts +9 -0
- package/src/input/commands/operator-runtime.ts +10 -12
- package/src/input/commands/permissions-provenance.ts +3 -3
- package/src/input/commands/planning-runtime.ts +7 -7
- package/src/input/commands/platform-sandbox-runtime.ts +2 -2
- package/src/input/commands/platform-services-runtime.ts +1 -1
- package/src/input/commands/policy-dispatch.ts +2 -2
- package/src/input/commands/policy.ts +3 -3
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/review-runtime.ts +39 -94
- package/src/input/commands/rewind-runtime.ts +81 -108
- package/src/input/commands/runtime-services.ts +15 -17
- package/src/input/commands/services-runtime.ts +2 -2
- package/src/input/commands/session-content.ts +21 -5
- package/src/input/commands/session-workflow.ts +13 -38
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/shell-core.ts +19 -4
- package/src/input/commands/skills-runtime.ts +2 -2
- package/src/input/commands/status-runtime.ts +13 -0
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +2 -8
- package/src/input/commands/work-plan-runtime.ts +4 -4
- package/src/input/commands/workstream-attempts.ts +24 -47
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands/worktree-runtime.ts +3 -3
- package/src/input/commands.ts +3 -1
- package/src/input/composer-editor.ts +44 -0
- package/src/input/concealed-input.ts +1 -1
- package/src/input/config-modal-types.ts +20 -8
- package/src/input/config-modal.ts +106 -105
- package/src/input/confirm-dialog-types.ts +28 -0
- package/src/input/confirm-dialog.ts +70 -0
- package/src/input/delete-key-policy.ts +3 -3
- package/src/input/feed-context-factory.ts +15 -16
- package/src/input/handler-command-route.ts +7 -42
- package/src/input/handler-content-actions.ts +16 -15
- package/src/input/handler-feed-routes.ts +26 -211
- package/src/input/handler-feed.ts +68 -95
- package/src/input/handler-interactions.ts +5 -3
- package/src/input/handler-modal-routes.ts +215 -216
- package/src/input/handler-modal-stack.ts +81 -11
- package/src/input/handler-modal-token-routes.ts +4 -0
- package/src/input/handler-onboarding.ts +0 -1
- package/src/input/handler-picker-routes.ts +65 -107
- package/src/input/handler-session-view-route.ts +149 -0
- package/src/input/handler-shortcuts.ts +16 -154
- package/src/input/handler-types.ts +4 -2
- package/src/input/handler-ui-state.ts +45 -15
- package/src/input/handler-work-tree-route.ts +123 -0
- package/src/input/handler.ts +25 -70
- package/src/input/keybindings.ts +16 -71
- package/src/input/masked-entry-modal.ts +117 -0
- package/src/input/mcp-workspace.ts +48 -17
- package/src/input/model-picker-filter.ts +8 -0
- package/src/input/model-picker-open.ts +99 -0
- package/src/input/model-picker-types.ts +1 -1
- package/src/input/model-picker.ts +92 -9
- package/src/input/notifications-modal.ts +123 -0
- package/src/input/overlay-filter.ts +26 -0
- package/src/input/profile-picker-modal.ts +37 -8
- package/src/input/search.ts +3 -3
- package/src/input/selection-modal-queue.ts +2 -0
- package/src/input/selection-modal.ts +28 -16
- package/src/input/session-picker-modal.ts +33 -8
- package/src/input/settings-modal-activation.ts +7 -2
- package/src/input/settings-modal-data.ts +11 -6
- package/src/input/settings-modal-types.ts +1 -1
- package/src/input/settings-modal.ts +20 -20
- package/src/input/submission-intent.ts +1 -1
- package/src/input/submission-router.ts +4 -9
- package/src/input/surface-modal-host.ts +110 -0
- package/src/input/theme-settings-actions.ts +82 -0
- package/src/input/usage-modal.ts +183 -0
- package/src/input/views.ts +97 -0
- package/src/main.ts +175 -165
- package/src/permissions/hunk-selection.ts +1 -1
- package/src/permissions/prompt-card.ts +511 -0
- package/src/permissions/prompt.ts +59 -622
- package/src/renderer/agents-modal.ts +310 -0
- package/src/renderer/autocomplete-overlay.ts +42 -220
- package/src/renderer/back-to-bottom.ts +80 -0
- package/src/renderer/block-actions-overlay.ts +30 -48
- package/src/renderer/bookmark-modal.ts +48 -79
- package/src/renderer/changes-modal.ts +325 -0
- package/src/renderer/code-block.ts +184 -106
- package/src/renderer/command-palette.ts +93 -0
- package/src/renderer/composer-fragments.ts +4 -4
- package/src/renderer/composer.ts +146 -0
- package/src/renderer/compositor.ts +26 -160
- package/src/renderer/config-modal.ts +185 -81
- package/src/renderer/confirm-dialog.ts +50 -0
- package/src/renderer/context-inspector.ts +116 -154
- package/src/renderer/context-status-hint.ts +4 -4
- package/src/renderer/conversation-layout.ts +20 -0
- package/src/renderer/conversation-overlays.ts +94 -84
- package/src/renderer/conversation-surface.ts +19 -38
- package/src/renderer/diff-tint.ts +98 -0
- package/src/renderer/file-picker-overlay.ts +63 -90
- package/src/renderer/footer-targets.ts +38 -0
- package/src/renderer/git-status.ts +3 -4
- package/src/renderer/header-line.ts +140 -117
- package/src/renderer/help-overlay.ts +133 -219
- package/src/renderer/history-search-overlay.ts +15 -12
- package/src/renderer/lane-graph/bead.ts +800 -0
- package/src/renderer/lane-graph/glyphs.ts +134 -0
- package/src/renderer/lane-graph/layout.ts +248 -0
- package/src/renderer/lane-graph/paint.ts +537 -0
- package/src/renderer/lane-graph/semantic-memo.ts +77 -0
- package/src/renderer/layout-engine.ts +2 -57
- package/src/renderer/layout.ts +7 -13
- package/src/renderer/markdown-inline.ts +2 -1
- package/src/renderer/markdown-table.ts +17 -12
- package/src/renderer/markdown.ts +92 -34
- package/src/renderer/masked-entry-modal.ts +78 -0
- package/src/renderer/mcp-workspace.ts +166 -203
- package/src/renderer/model-workspace.ts +381 -436
- package/src/renderer/notifications-modal.ts +116 -0
- package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
- package/src/renderer/process-view.ts +192 -0
- package/src/renderer/profile-picker-modal.ts +58 -110
- package/src/renderer/search-overlay.ts +6 -3
- package/src/renderer/selection-modal-overlay.ts +87 -327
- package/src/renderer/semantic-diff.ts +2 -2
- package/src/renderer/session-chips.ts +102 -0
- package/src/renderer/session-picker-modal.ts +177 -297
- package/src/renderer/settings-modal-connections.ts +14 -23
- package/src/renderer/settings-modal-context.ts +367 -0
- package/src/renderer/settings-modal-helpers.ts +6 -11
- package/src/renderer/settings-modal.ts +322 -726
- package/src/renderer/shell-surface.ts +277 -175
- package/src/renderer/shortcuts-overlay.ts +217 -0
- package/src/renderer/status-line.ts +475 -0
- package/src/renderer/surface-compose.ts +174 -0
- package/src/renderer/surface-kit-extra.ts +303 -0
- package/src/renderer/surface-kit-list.ts +366 -0
- package/src/renderer/surface-kit-parts.ts +360 -0
- package/src/renderer/surface-kit.ts +568 -0
- package/src/renderer/syntax-highlighter.ts +280 -197
- package/src/renderer/syntax-theme.ts +51 -0
- package/src/renderer/system-message.ts +0 -1
- package/src/renderer/term-caps.ts +42 -1
- package/src/renderer/terminal-bg-probe.ts +116 -19
- package/src/renderer/terminal-palette-probe.ts +322 -0
- package/src/renderer/terminal-palette-reader.ts +183 -0
- package/src/renderer/terminal-palette.ts +58 -0
- package/src/renderer/theme-mode-config.ts +16 -1
- package/src/renderer/theme.ts +234 -207
- package/src/renderer/thinking.ts +0 -1
- package/src/renderer/throbber.ts +188 -0
- package/src/renderer/toast-center.ts +119 -0
- package/src/renderer/ui-factory.ts +39 -602
- package/src/renderer/ui-primitives.ts +8 -25
- package/src/renderer/usage-modal.ts +353 -0
- package/src/renderer/user-message.ts +58 -0
- package/src/renderer/voice-capture-chip.ts +53 -0
- package/src/runtime/bootstrap-command-context.ts +0 -5
- package/src/runtime/bootstrap-command-parts.ts +38 -89
- package/src/runtime/bootstrap-core.ts +13 -15
- package/src/runtime/bootstrap-hook-bridge.ts +12 -21
- package/src/runtime/bootstrap-notifier-sync.ts +17 -1
- package/src/runtime/bootstrap-shell.ts +16 -50
- package/src/runtime/bootstrap.ts +14 -14
- package/src/runtime/client/fleet-union.ts +5 -5
- package/src/runtime/client/hosted-exit.ts +2 -2
- package/src/runtime/client/hosted-sessions.ts +1 -1
- package/src/runtime/client/spine-adoption.ts +1 -1
- package/src/runtime/context-accounting-source.ts +5 -4
- package/src/runtime/index.ts +1 -0
- package/src/runtime/notification-dispatch.ts +102 -29
- package/src/runtime/recovery-offer-wiring.ts +4 -1
- package/src/runtime/recovery-prompt.ts +31 -15
- package/src/runtime/runtime-services-types.ts +10 -6
- package/src/runtime/services.ts +17 -12
- package/src/runtime/session-continuity-hints.ts +2 -5
- package/src/runtime/session-inbound-inputs.ts +1 -1
- package/src/runtime/session-resume-liveness-confirm.ts +14 -2
- package/src/runtime/startup-typeahead-gate.ts +103 -0
- package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
- package/src/runtime/store/domains/index.ts +0 -8
- package/src/runtime/store/selectors/index.ts +0 -28
- package/src/runtime/store/state.ts +0 -8
- package/src/runtime/ui-services.ts +4 -6
- package/src/runtime/usage-tracker.ts +348 -0
- package/src/runtime/wrfc-persistence.ts +4 -4
- package/src/shell/blocking-input.ts +92 -2
- package/src/shell/recovery-input-helpers.ts +1 -1
- package/src/shell/session-views.ts +539 -0
- package/src/shell/status-report.ts +86 -0
- package/src/shell/transcript-scroll.ts +68 -0
- package/src/shell/ui-openers.ts +84 -172
- package/src/shell/view-openers.ts +205 -0
- package/src/tools/wrfc-agent-guard.ts +22 -2
- package/src/utils/format-duration.ts +4 -8
- package/src/utils/splash-lines.ts +9 -1
- package/src/utils/terminal-width.ts +56 -0
- package/src/verification/verification-ledger.ts +18 -16
- package/src/{panels → views}/agent-inspector-shared.ts +2 -2
- package/src/{panels → views}/builtin-modals.ts +22 -21
- package/src/views/builtin-views.ts +135 -0
- package/src/{panels → views}/confirm-state.ts +9 -9
- package/src/{panels → views}/diff-review-model.ts +10 -3
- package/src/{panels → views}/eval-registry.ts +1 -1
- package/src/{panels → views}/fleet-acts.ts +49 -85
- package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
- package/src/{panels → views}/fleet-gateway.ts +5 -5
- package/src/{panels → views}/fleet-observed-render.ts +16 -16
- package/src/{panels → views}/fleet-read-model.ts +9 -9
- package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
- package/src/{panels → views}/fleet-spawn.ts +33 -49
- package/src/{panels → views}/fleet-steer.ts +12 -12
- package/src/{panels → views}/fleet-stop.ts +4 -4
- package/src/{panels → views}/fleet-transcript.ts +26 -27
- package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
- package/src/{panels → views}/hosted-session-feed.ts +6 -6
- package/src/views/modal-surface-registry.ts +41 -0
- package/src/{panels → views}/modals/devices-modal.ts +2 -2
- package/src/{panels → views}/modals/hooks-modal.ts +13 -13
- package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
- package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
- package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
- package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
- package/src/{panels → views}/modals/memory-modal.ts +8 -8
- package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
- package/src/{panels → views}/modals/pairing-modal.ts +9 -9
- package/src/{panels → views}/modals/planning-modal.ts +15 -20
- package/src/{panels → views}/modals/plugins-modal.ts +6 -6
- package/src/{panels → views}/modals/policy-modal.ts +13 -5
- package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
- package/src/{panels → views}/modals/remote-modal.ts +3 -3
- package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
- package/src/{panels → views}/modals/security-modal.ts +22 -16
- package/src/{panels → views}/modals/services-modal.ts +3 -3
- package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
- package/src/{panels → views}/modals/skills-modal.ts +9 -9
- package/src/{panels → views}/modals/subscription-modal.ts +3 -3
- package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
- package/src/views/notifications-feed.ts +314 -0
- package/src/{panels → views}/polish-core.ts +38 -36
- package/src/{panels → views}/polish-tables.ts +35 -26
- package/src/{panels → views}/polish.ts +101 -95
- package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
- package/src/{panels → views}/provider-health-routes.ts +14 -14
- package/src/{panels → views}/search-focus.ts +8 -8
- package/src/{panels → views}/session-maintenance.ts +11 -11
- package/src/views/skills-discovery.ts +159 -0
- package/src/views/view-deps.ts +80 -0
- package/src/views/view-panel-adapter.ts +53 -0
- package/src/{panels → views}/workstream-graph-render.ts +1 -1
- package/src/core/conversation-fold.ts +0 -101
- package/src/core/conversation-turn-structure.ts +0 -464
- package/src/core/conversation-user-receipts.ts +0 -42
- package/src/input/commands/operator-panel-runtime.ts +0 -186
- package/src/input/panel-integration-actions.ts +0 -33
- package/src/input/panel-mouse-geometry.ts +0 -97
- package/src/input/panel-paste-flood-guard.ts +0 -86
- package/src/panels/base-panel.ts +0 -301
- package/src/panels/builtin/agent.ts +0 -27
- package/src/panels/builtin/development.ts +0 -92
- package/src/panels/builtin/knowledge.ts +0 -12
- package/src/panels/builtin/operations.ts +0 -177
- package/src/panels/builtin/session.ts +0 -81
- package/src/panels/builtin/shared.ts +0 -357
- package/src/panels/builtin-panels.ts +0 -28
- package/src/panels/cost-tracker-panel.ts +0 -746
- package/src/panels/diff-panel.ts +0 -797
- package/src/panels/diff-review-panel.ts +0 -395
- package/src/panels/expandable-list-panel.ts +0 -189
- package/src/panels/fleet-deep-link.ts +0 -31
- package/src/panels/fleet-panel.ts +0 -799
- package/src/panels/git-panel.ts +0 -800
- package/src/panels/hosted-session-panel.ts +0 -221
- package/src/panels/index.ts +0 -21
- package/src/panels/local-auth-panel.ts +0 -478
- package/src/panels/notifications-feed.ts +0 -127
- package/src/panels/notifications-panel.ts +0 -160
- package/src/panels/panel-confirm-overlay.ts +0 -72
- package/src/panels/panel-manager.ts +0 -791
- package/src/panels/plugins-panel.ts +0 -371
- package/src/panels/scrollable-list-panel.ts +0 -498
- package/src/panels/skills-panel.ts +0 -454
- package/src/panels/token-budget-panel.ts +0 -688
- package/src/panels/types.ts +0 -191
- package/src/panels/wrfc-panel-format.ts +0 -133
- package/src/renderer/diff-view.ts +0 -134
- package/src/renderer/fleet-tab-strip.ts +0 -56
- package/src/renderer/footer-tips.ts +0 -53
- package/src/renderer/fullscreen-primitives.ts +0 -146
- package/src/renderer/fullscreen-workspace.ts +0 -194
- package/src/renderer/hint-grammar.ts +0 -52
- package/src/renderer/modal-factory.ts +0 -480
- package/src/renderer/overlay-box.ts +0 -154
- package/src/renderer/overlay-viewport.ts +0 -104
- package/src/renderer/panel-composite.ts +0 -145
- package/src/renderer/panel-workspace-bar.ts +0 -54
- package/src/renderer/process-indicator.ts +0 -163
- package/src/renderer/tab-strip.ts +0 -222
- package/src/renderer/text-layout.ts +0 -31
- package/src/renderer/tool-call.ts +0 -276
- package/src/renderer/tool-result-expanded-lines.ts +0 -48
- package/src/renderer/tool-result-summary.ts +0 -219
- package/src/runtime/diagnostics/panels/index.ts +0 -21
- package/src/runtime/diagnostics/panels/ops.ts +0 -156
- package/src/runtime/perf/panel-contracts.ts +0 -32
- package/src/runtime/store/domains/panels.ts +0 -117
- /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
- /package/src/{panels → views}/modals/modal-theme.ts +0 -0
- /package/src/{panels → views}/provider-health-domains.ts +0 -0
|
@@ -1,688 +0,0 @@
|
|
|
1
|
-
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
-
import { BasePanel } from './base-panel.ts';
|
|
3
|
-
import { createEmptyLine, createStyledCell, type Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
4
|
-
import type { Orchestrator } from '@pellux/goodvibes-sdk/platform/core';
|
|
5
|
-
import { evaluateSessionMaintenance } from '@/runtime/index.ts';
|
|
6
|
-
import type { TurnEvent } from '@/runtime/index.ts';
|
|
7
|
-
import type { UiEventFeed } from '@/runtime/index.ts';
|
|
8
|
-
import type { UiReadModel, UiSessionSnapshot } from '../runtime/ui-read-models.ts';
|
|
9
|
-
import type { SessionMemoryQuery } from '@/runtime/index.ts';
|
|
10
|
-
import { calcSessionCost, describePricingSource, isModelPriced } from '@pellux/goodvibes-sdk/platform/providers';
|
|
11
|
-
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
12
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
13
|
-
import {
|
|
14
|
-
buildEmptyState,
|
|
15
|
-
buildGuidanceLine,
|
|
16
|
-
buildKeyboardHints,
|
|
17
|
-
buildMeterLine,
|
|
18
|
-
buildStatusPill,
|
|
19
|
-
buildStyledPanelLine,
|
|
20
|
-
buildTable,
|
|
21
|
-
buildPanelWorkspace,
|
|
22
|
-
resolveScrollablePanelSection,
|
|
23
|
-
DEFAULT_PANEL_PALETTE,
|
|
24
|
-
extendPalette,
|
|
25
|
-
type PanelWorkspaceSection,
|
|
26
|
-
} from './polish.ts';
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Types
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
interface TurnUsage {
|
|
33
|
-
input: number;
|
|
34
|
-
output: number;
|
|
35
|
-
cacheRead: number;
|
|
36
|
-
cacheWrite: number;
|
|
37
|
-
/** Timestamp of the turn completion. */
|
|
38
|
-
ts: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
// Colors
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
|
|
45
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
46
|
-
// Token data-series colors (input/output/cache), categorical, no shared equivalent
|
|
47
|
-
input: '#00ffff',
|
|
48
|
-
output: '#d000ff',
|
|
49
|
-
cacheRead: '#00d700',
|
|
50
|
-
cacheWrite: '#ffaf00',
|
|
51
|
-
// Progress-bar track shade and per-turn table sub-header gray
|
|
52
|
-
barBg: '236',
|
|
53
|
-
turnHeader: '242',
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Warning thresholds for context window usage
|
|
57
|
-
const WARN_YELLOW = 0.70;
|
|
58
|
-
const WARN_RED = 0.90;
|
|
59
|
-
|
|
60
|
-
// Maximum turns to keep in per-turn history
|
|
61
|
-
const MAX_TURN_HISTORY = 10;
|
|
62
|
-
|
|
63
|
-
// Rows scrolled per pageup/pagedown key press through the Recent Turns history.
|
|
64
|
-
const PAGE_SCROLL_ROWS = 5;
|
|
65
|
-
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
// Helpers
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
|
|
70
|
-
/** Format a token count: up to 9999 shown as-is, then 10.0k, 1.2M, etc. */
|
|
71
|
-
function fmtTok(n: number): string {
|
|
72
|
-
if (n < 10_000) return String(n);
|
|
73
|
-
if (n < 1_000_000) return (n / 1000).toFixed(1) + 'k';
|
|
74
|
-
return (n / 1_000_000).toFixed(2) + 'M';
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// TokenBudgetPanel
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Real-time token usage breakdown panel.
|
|
83
|
-
*
|
|
84
|
-
* Displays:
|
|
85
|
-
* - Stacked bar: input / output / cache-read / cache-write
|
|
86
|
-
* - Context window fill percentage with progress bar + pressure pill
|
|
87
|
-
* - Per-turn token usage (last 10 turns), scrollable
|
|
88
|
-
* - Cumulative session totals + inline cost estimate
|
|
89
|
-
* - Warning threshold indicators (yellow at 70%, red at 90%)
|
|
90
|
-
*
|
|
91
|
-
* Absorbs the former ContextVisualizerPanel ('context'): the pressure pill,
|
|
92
|
-
* TURN_SUBMITTED/TURN_COMPLETED-driven refresh, and the compact-now action
|
|
93
|
-
* all live here now. `PanelManager` aliases the retired `context` id to
|
|
94
|
-
* `tokens` so old `/panel open context` calls keep working.
|
|
95
|
-
*/
|
|
96
|
-
export class TokenBudgetPanel extends BasePanel {
|
|
97
|
-
/** Snapshot of cumulative usage from the Orchestrator after each turn. */
|
|
98
|
-
private sessionUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
99
|
-
/** lastInputTokens from the Orchestrator, current context window occupancy. */
|
|
100
|
-
private lastInputTokens = 0;
|
|
101
|
-
/** Context window size from the model config (0 = unknown). */
|
|
102
|
-
private contextWindow = 0;
|
|
103
|
-
/** Per-turn snapshots, at most MAX_TURN_HISTORY entries. */
|
|
104
|
-
private turnHistory: TurnUsage[] = [];
|
|
105
|
-
/** Previous cumulative snapshot to compute per-turn delta. */
|
|
106
|
-
private prevCumulative = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
107
|
-
|
|
108
|
-
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
109
|
-
private orchestrator: Orchestrator | null = null;
|
|
110
|
-
private getContextWindow: (() => number) | null = null;
|
|
111
|
-
private sessionReadModel: UiReadModel<UiSessionSnapshot> | null = null;
|
|
112
|
-
/** Resolver for the active model id, for inline cost display. */
|
|
113
|
-
private getCurrentModelId: (() => string) | null = null;
|
|
114
|
-
private readonly sessionMemoryStore: SessionMemoryQuery;
|
|
115
|
-
private readonly configManager: Pick<ConfigManager, 'get'>;
|
|
116
|
-
private readonly requestRender: () => void;
|
|
117
|
-
|
|
118
|
-
/** Turn-event bus subscriptions, absorbed from ContextVisualizerPanel. */
|
|
119
|
-
private unsubs: Array<() => void> = [];
|
|
120
|
-
|
|
121
|
-
/** Manual scroll cursor into turnHistory; -1 = auto-follow the latest turn. */
|
|
122
|
-
private turnScrollIndex = -1;
|
|
123
|
-
|
|
124
|
-
/** Pending confirm dialog (currently only used for the compact-now action). */
|
|
125
|
-
private confirm: ConfirmState<'compact'> | null = null;
|
|
126
|
-
/**
|
|
127
|
-
* Set when a confirm resolves to 'confirmed'; consumed by
|
|
128
|
-
* handlePanelIntegrationAction on the very next dispatch of that same key
|
|
129
|
-
* so it can reach the ctx.executeCommand bridge (handleInput has no ctx).
|
|
130
|
-
*/
|
|
131
|
-
private compactConfirmedPending = false;
|
|
132
|
-
|
|
133
|
-
constructor(
|
|
134
|
-
sessionMemoryStore: SessionMemoryQuery,
|
|
135
|
-
configManager: Pick<ConfigManager, 'get'>,
|
|
136
|
-
requestRender: () => void = () => {},
|
|
137
|
-
turnEvents?: UiEventFeed<TurnEvent>,
|
|
138
|
-
) {
|
|
139
|
-
super('tokens', 'Tokens', '▢', 'providers');
|
|
140
|
-
this.sessionMemoryStore = sessionMemoryStore;
|
|
141
|
-
this.configManager = configManager;
|
|
142
|
-
this.requestRender = requestRender;
|
|
143
|
-
if (turnEvents) this._attachBus(turnEvents);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---------------------------------------------------------------------------
|
|
147
|
-
// External wiring
|
|
148
|
-
// ---------------------------------------------------------------------------
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Wire the panel to live data sources. Call once after construction.
|
|
152
|
-
*
|
|
153
|
-
* @param orchestrator The main Orchestrator instance (for usage + lastInputTokens).
|
|
154
|
-
* @param getCtxWindow Callback returning the current model's contextWindow value.
|
|
155
|
-
* @param sessionReadModel Session read model (for the Maintenance status line).
|
|
156
|
-
* @param getCurrentModelId Callback returning the active model id, for the inline cost line.
|
|
157
|
-
*/
|
|
158
|
-
wire(
|
|
159
|
-
orchestrator: Orchestrator,
|
|
160
|
-
getCtxWindow: () => number,
|
|
161
|
-
sessionReadModel?: UiReadModel<UiSessionSnapshot>,
|
|
162
|
-
getCurrentModelId?: () => string,
|
|
163
|
-
): void {
|
|
164
|
-
this.orchestrator = orchestrator;
|
|
165
|
-
this.getContextWindow = getCtxWindow;
|
|
166
|
-
this.sessionReadModel = sessionReadModel ?? null;
|
|
167
|
-
this.getCurrentModelId = getCurrentModelId ?? null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Snapshot a completed turn into per-turn history.
|
|
172
|
-
* Should be called from a `turn:complete` event listener.
|
|
173
|
-
*/
|
|
174
|
-
recordTurn(): void {
|
|
175
|
-
if (!this.orchestrator) return;
|
|
176
|
-
|
|
177
|
-
const cu = this.orchestrator.usage;
|
|
178
|
-
const delta: TurnUsage = {
|
|
179
|
-
input: cu.input - this.prevCumulative.input,
|
|
180
|
-
output: cu.output - this.prevCumulative.output,
|
|
181
|
-
cacheRead: cu.cacheRead - this.prevCumulative.cacheRead,
|
|
182
|
-
cacheWrite: cu.cacheWrite - this.prevCumulative.cacheWrite,
|
|
183
|
-
ts: Date.now(),
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
this.prevCumulative = { ...cu };
|
|
187
|
-
|
|
188
|
-
this.turnHistory.push(delta);
|
|
189
|
-
if (this.turnHistory.length > MAX_TURN_HISTORY) {
|
|
190
|
-
this.turnHistory.shift();
|
|
191
|
-
}
|
|
192
|
-
// A freshly completed turn always pulls the view back to the latest entry.
|
|
193
|
-
this.turnScrollIndex = -1;
|
|
194
|
-
|
|
195
|
-
this._refreshAndRender();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
|
-
// Turn-event bus (absorbed from ContextVisualizerPanel)
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
|
|
202
|
-
private _attachBus(turnEvents: UiEventFeed<TurnEvent>): void {
|
|
203
|
-
if (this.unsubs.length > 0) return;
|
|
204
|
-
this.unsubs.push(turnEvents.on('TURN_COMPLETED', () => this.recordTurn()));
|
|
205
|
-
this.unsubs.push(turnEvents.on('TURN_SUBMITTED', () => this._refreshAndRender()));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private _detachBus(): void {
|
|
209
|
-
for (const unsub of this.unsubs) unsub();
|
|
210
|
-
this.unsubs = [];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
// Lifecycle
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
override onActivate(): void {
|
|
218
|
-
super.onActivate();
|
|
219
|
-
this.refresh();
|
|
220
|
-
// Poll every 2 s while active so the context bar stays current during streaming
|
|
221
|
-
if (this.refreshTimer !== null) clearInterval(this.refreshTimer);
|
|
222
|
-
this.refreshTimer = setInterval(() => this._refreshAndRender(), 2_000);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
override onDeactivate(): void {
|
|
226
|
-
super.onDeactivate();
|
|
227
|
-
this.confirm = null;
|
|
228
|
-
// Stop the off-screen poll so a backgrounded Tokens tab does not keep
|
|
229
|
-
// refreshing (and, once requestRender is wired, force a repaint) every 2 s.
|
|
230
|
-
if (this.refreshTimer !== null) {
|
|
231
|
-
clearInterval(this.refreshTimer);
|
|
232
|
-
this.refreshTimer = null;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
override onDestroy(): void {
|
|
237
|
-
if (this.refreshTimer !== null) {
|
|
238
|
-
clearInterval(this.refreshTimer);
|
|
239
|
-
this.refreshTimer = null;
|
|
240
|
-
}
|
|
241
|
-
this._detachBus();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ---------------------------------------------------------------------------
|
|
245
|
-
// Input
|
|
246
|
-
// ---------------------------------------------------------------------------
|
|
247
|
-
|
|
248
|
-
private _pressureRatio(): number {
|
|
249
|
-
return this.contextWindow > 0 ? this.lastInputTokens / this.contextWindow : 0;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private _pressureElevated(): boolean {
|
|
253
|
-
return this.contextWindow > 0 && this._pressureRatio() >= WARN_YELLOW;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private _scrollTurns(delta: number): void {
|
|
257
|
-
const current = this.turnScrollIndex === -1 ? this.turnHistory.length : this.turnScrollIndex;
|
|
258
|
-
const next = current + delta;
|
|
259
|
-
this.turnScrollIndex = Math.max(0, Math.min(this.turnHistory.length, next));
|
|
260
|
-
this.markDirty();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
handleInput(key: string): boolean {
|
|
264
|
-
// Confirmation dialog, use the shared handleConfirmInput for y/n/Esc UX
|
|
265
|
-
const confirmResult = handleConfirmInput(this.confirm, key);
|
|
266
|
-
if (confirmResult === 'confirmed') {
|
|
267
|
-
this.confirm = null;
|
|
268
|
-
this.compactConfirmedPending = true;
|
|
269
|
-
this.markDirty();
|
|
270
|
-
return true;
|
|
271
|
-
}
|
|
272
|
-
if (confirmResult === 'cancelled') {
|
|
273
|
-
this.confirm = null;
|
|
274
|
-
this.markDirty();
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
if (confirmResult === 'absorbed') return true;
|
|
278
|
-
|
|
279
|
-
if (key === 'C' && this._pressureElevated()) {
|
|
280
|
-
this.confirm = { subject: 'compact', label: 'Compact context now', verb: 'Compact' };
|
|
281
|
-
this.markDirty();
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (this.turnHistory.length > 0) {
|
|
286
|
-
switch (key) {
|
|
287
|
-
case 'up': this._scrollTurns(-1); return true;
|
|
288
|
-
case 'down': this._scrollTurns(1); return true;
|
|
289
|
-
case 'pageup': this._scrollTurns(-PAGE_SCROLL_ROWS); return true;
|
|
290
|
-
case 'pagedown': this._scrollTurns(PAGE_SCROLL_ROWS); return true;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (key === 'r') {
|
|
295
|
-
this._refreshAndRender();
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Executes the compact-now action once the confirm dialog resolves. This
|
|
304
|
-
* lives in the integration hook (rather than inline in handleInput)
|
|
305
|
-
* because only this hook carries the `ctx.executeCommand` bridge into the
|
|
306
|
-
* real `/compact` command.
|
|
307
|
-
*/
|
|
308
|
-
handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
309
|
-
if (!this.compactConfirmedPending) return false;
|
|
310
|
-
this.compactConfirmedPending = false;
|
|
311
|
-
void ctx.executeCommand?.('compact', []);
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
|
-
// Data refresh
|
|
317
|
-
// ---------------------------------------------------------------------------
|
|
318
|
-
|
|
319
|
-
private refresh(): void {
|
|
320
|
-
if (this.orchestrator) {
|
|
321
|
-
const u = this.orchestrator.usage;
|
|
322
|
-
this.sessionUsage.input = u.input ?? 0;
|
|
323
|
-
this.sessionUsage.output = u.output ?? 0;
|
|
324
|
-
this.sessionUsage.cacheRead = u.cacheRead ?? 0;
|
|
325
|
-
this.sessionUsage.cacheWrite = u.cacheWrite ?? 0;
|
|
326
|
-
this.lastInputTokens = this.orchestrator.lastInputTokens;
|
|
327
|
-
}
|
|
328
|
-
if (this.getContextWindow) {
|
|
329
|
-
this.contextWindow = this.getContextWindow();
|
|
330
|
-
}
|
|
331
|
-
this.markDirty();
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Refresh data and request a repaint. Used by the background poll and on
|
|
336
|
-
* turn completion, where no key event will otherwise drive a render.
|
|
337
|
-
*/
|
|
338
|
-
private _refreshAndRender(): void {
|
|
339
|
-
this.refresh();
|
|
340
|
-
this.requestRender();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// ---------------------------------------------------------------------------
|
|
344
|
-
// Rendering
|
|
345
|
-
// ---------------------------------------------------------------------------
|
|
346
|
-
|
|
347
|
-
override render(width: number, height: number): Line[] {
|
|
348
|
-
return this.trackedRender(() => {
|
|
349
|
-
if (this.confirm) {
|
|
350
|
-
return buildPanelWorkspace(width, height, {
|
|
351
|
-
title: ' Token Budget',
|
|
352
|
-
sections: [{
|
|
353
|
-
title: 'Confirmation',
|
|
354
|
-
lines: renderConfirmLines(width, this.confirm),
|
|
355
|
-
}],
|
|
356
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const sections: PanelWorkspaceSection[] = [];
|
|
361
|
-
|
|
362
|
-
if (this.contextWindow > 0) {
|
|
363
|
-
sections.push({
|
|
364
|
-
title: 'Context',
|
|
365
|
-
lines: this.renderContextBar(width),
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
sections.push({
|
|
370
|
-
title: 'Session',
|
|
371
|
-
lines: [
|
|
372
|
-
...this.renderStackedBar(width, this.sessionUsage, 'Session'),
|
|
373
|
-
...this.renderTotals(width),
|
|
374
|
-
],
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
const elevated = this._pressureElevated();
|
|
378
|
-
const footerHints = elevated
|
|
379
|
-
? [
|
|
380
|
-
{ keys: 'C', label: 'compact now' },
|
|
381
|
-
{ keys: 'up/down', label: 'scroll turns' },
|
|
382
|
-
{ keys: 'r', label: 'refresh' },
|
|
383
|
-
]
|
|
384
|
-
: [
|
|
385
|
-
{ keys: 'up/down', label: 'scroll turns' },
|
|
386
|
-
{ keys: 'r', label: 'refresh' },
|
|
387
|
-
{ keys: '/compact', label: 'compress context' },
|
|
388
|
-
];
|
|
389
|
-
|
|
390
|
-
if (this.turnHistory.length > 0) {
|
|
391
|
-
const priorSections = [...sections];
|
|
392
|
-
const scrollIndex = this.turnScrollIndex === -1 ? this.turnHistory.length : this.turnScrollIndex;
|
|
393
|
-
const turnsSection = resolveScrollablePanelSection(width, height, {
|
|
394
|
-
intro: 'Live context pressure, session token composition, cache usage, and recent turn deltas.',
|
|
395
|
-
footerLines: [
|
|
396
|
-
buildKeyboardHints(width, footerHints, DEFAULT_PANEL_PALETTE),
|
|
397
|
-
],
|
|
398
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
399
|
-
beforeSections: priorSections,
|
|
400
|
-
section: {
|
|
401
|
-
title: 'Recent Turns',
|
|
402
|
-
scrollableLines: this.renderTurnHistory(width, this.turnHistory.length + 1),
|
|
403
|
-
scrollOffset: scrollIndex,
|
|
404
|
-
minRows: 6,
|
|
405
|
-
},
|
|
406
|
-
afterSections: [{
|
|
407
|
-
title: 'Maintenance',
|
|
408
|
-
lines: this.renderMaintenance(width),
|
|
409
|
-
}],
|
|
410
|
-
});
|
|
411
|
-
sections.push(turnsSection.section);
|
|
412
|
-
} else {
|
|
413
|
-
sections.push({
|
|
414
|
-
title: 'Recent Turns',
|
|
415
|
-
lines: buildEmptyState(
|
|
416
|
-
width,
|
|
417
|
-
' No turns recorded yet',
|
|
418
|
-
'Token deltas appear here after completed turns so you can see context growth and cache usage.',
|
|
419
|
-
[],
|
|
420
|
-
DEFAULT_PANEL_PALETTE,
|
|
421
|
-
),
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
sections.push({
|
|
426
|
-
title: 'Maintenance',
|
|
427
|
-
lines: this.renderMaintenance(width),
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
return buildPanelWorkspace(width, height, {
|
|
431
|
-
title: ' Token Budget',
|
|
432
|
-
intro: 'Live context pressure, session token composition, cache usage, and recent turn deltas.',
|
|
433
|
-
sections,
|
|
434
|
-
footerLines: [
|
|
435
|
-
buildKeyboardHints(width, footerHints, DEFAULT_PANEL_PALETTE),
|
|
436
|
-
],
|
|
437
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
private renderMaintenance(width: number): Line[] {
|
|
443
|
-
const sessionSnapshot = this.sessionReadModel?.getSnapshot();
|
|
444
|
-
const status = evaluateSessionMaintenance({
|
|
445
|
-
configManager: this.configManager,
|
|
446
|
-
currentTokens: this.lastInputTokens,
|
|
447
|
-
contextWindow: this.contextWindow,
|
|
448
|
-
messageCount: sessionSnapshot?.totalTurns,
|
|
449
|
-
sessionMemoryCount: this.sessionMemoryStore.list().length,
|
|
450
|
-
session: sessionSnapshot?.session,
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
const lines: Line[] = [
|
|
454
|
-
this.paintTextLine(` ${status.summary}`, width, C.label),
|
|
455
|
-
];
|
|
456
|
-
|
|
457
|
-
if (status.reasons[0]) {
|
|
458
|
-
lines.push(this.paintTextLine(` ${status.reasons[0]}`, width, C.dim, { dim: true }));
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (status.guidanceMode !== 'off' && status.nextSteps.length > 0) {
|
|
462
|
-
const nextStep = status.nextSteps[0]!;
|
|
463
|
-
// when the recommended next step is /compact and C is armed
|
|
464
|
-
// (see handleInput's elevated-only C branch and the footer hint),
|
|
465
|
-
// advertise the key instead of the command, pressing C already
|
|
466
|
-
// dispatches it for real, so printing '/compact' here was a redundant
|
|
467
|
-
// action substitute.
|
|
468
|
-
if (nextStep === '/compact' && this._pressureElevated()) {
|
|
469
|
-
lines.push(buildGuidanceLine(width, 'C', 'compact context now: the recommended next maintenance action', DEFAULT_PANEL_PALETTE));
|
|
470
|
-
} else {
|
|
471
|
-
lines.push(buildGuidanceLine(width, nextStep, 'open the next maintenance action directly', DEFAULT_PANEL_PALETTE));
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return lines;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Renders a compact stacked proportion bar:
|
|
480
|
-
* [########............] (input=cyan, output=purple, cacheR=green, cacheW=yellow)
|
|
481
|
-
*/
|
|
482
|
-
private renderStackedBar(
|
|
483
|
-
width: number,
|
|
484
|
-
u: { input: number; output: number; cacheRead: number; cacheWrite: number },
|
|
485
|
-
label: string,
|
|
486
|
-
): Line[] {
|
|
487
|
-
const lines: Line[] = [];
|
|
488
|
-
|
|
489
|
-
const labelLine = this.paintTextLine(` ${label}:`, width, C.label);
|
|
490
|
-
lines.push(labelLine);
|
|
491
|
-
|
|
492
|
-
const total = u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
493
|
-
if (total === 0) {
|
|
494
|
-
lines.push(this.paintTextLine(' (no data)', width, C.dim, { dim: true }));
|
|
495
|
-
return lines;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const BAR_W = Math.max(10, Math.min(40, width - 4));
|
|
499
|
-
const segments: Array<{ color: string; label: string; count: number; cells: number }> = [
|
|
500
|
-
{ color: C.input, label: 'In', count: u.input, cells: 0 },
|
|
501
|
-
{ color: C.output, label: 'Out', count: u.output, cells: 0 },
|
|
502
|
-
{ color: C.cacheRead, label: 'CR', count: u.cacheRead, cells: 0 },
|
|
503
|
-
{ color: C.cacheWrite, label: 'CW', count: u.cacheWrite, cells: 0 },
|
|
504
|
-
].filter(s => s.count > 0);
|
|
505
|
-
|
|
506
|
-
// Distribute cells proportionally, ensuring they sum to BAR_W
|
|
507
|
-
let assigned = 0;
|
|
508
|
-
for (const seg of segments) {
|
|
509
|
-
seg.cells = Math.floor((seg.count / total) * BAR_W);
|
|
510
|
-
assigned += seg.cells;
|
|
511
|
-
}
|
|
512
|
-
// Give remainder to the largest segment
|
|
513
|
-
const remainder = BAR_W - assigned;
|
|
514
|
-
if (remainder > 0 && segments.length > 0) {
|
|
515
|
-
const largest = segments.reduce((a, b) => (b.count > a.count ? b : a));
|
|
516
|
-
largest.cells += remainder;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Build bar line
|
|
520
|
-
const usedCells = segments.reduce((sum, seg) => sum + seg.cells, 0);
|
|
521
|
-
lines.push(buildStyledPanelLine(width, [
|
|
522
|
-
{ text: ' [', fg: C.label },
|
|
523
|
-
...segments.map((seg) => ({ text: '#'.repeat(seg.cells), fg: seg.color })),
|
|
524
|
-
{ text: '.'.repeat(Math.max(0, BAR_W - usedCells)), fg: C.barBg },
|
|
525
|
-
{ text: ']', fg: C.label },
|
|
526
|
-
]));
|
|
527
|
-
|
|
528
|
-
// Legend line: In:12.3k Out:4.5k CR:6.7k CW:1.2k Total:24.7k
|
|
529
|
-
const allSegs = [
|
|
530
|
-
{ color: C.input, label: 'In', count: u.input },
|
|
531
|
-
{ color: C.output, label: 'Out', count: u.output },
|
|
532
|
-
{ color: C.cacheRead, label: 'CR', count: u.cacheRead },
|
|
533
|
-
{ color: C.cacheWrite, label: 'CW', count: u.cacheWrite },
|
|
534
|
-
];
|
|
535
|
-
lines.push(buildStyledPanelLine(width, [
|
|
536
|
-
{ text: ' ', fg: C.dim },
|
|
537
|
-
...allSegs.flatMap((seg) => ([
|
|
538
|
-
{ text: `${seg.label}:`, fg: seg.color },
|
|
539
|
-
{ text: `${fmtTok(seg.count)} `, fg: C.value },
|
|
540
|
-
])),
|
|
541
|
-
{ text: ` Total:${fmtTok(total)}`, fg: C.value, bold: true },
|
|
542
|
-
]));
|
|
543
|
-
|
|
544
|
-
return lines;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/** Renders a full-width progress bar for context window fill, plus a pressure pill (absorbed from ContextVisualizerPanel). */
|
|
548
|
-
private renderContextBar(width: number): Line[] {
|
|
549
|
-
const lines: Line[] = [];
|
|
550
|
-
const pct = this.contextWindow > 0
|
|
551
|
-
? Math.min(1, this.lastInputTokens / this.contextWindow)
|
|
552
|
-
: 0;
|
|
553
|
-
const pctInt = Math.round(pct * 100);
|
|
554
|
-
|
|
555
|
-
// Choose color based on thresholds
|
|
556
|
-
let barColor: string;
|
|
557
|
-
let warnSuffix = '';
|
|
558
|
-
if (pct >= WARN_RED) {
|
|
559
|
-
barColor = C.bad;
|
|
560
|
-
warnSuffix = ' ! CRITICAL';
|
|
561
|
-
} else if (pct >= WARN_YELLOW) {
|
|
562
|
-
barColor = C.warn;
|
|
563
|
-
warnSuffix = ' ! HIGH';
|
|
564
|
-
} else {
|
|
565
|
-
barColor = C.good;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const label = ' Context: ';
|
|
569
|
-
const suffix = ` ${fmtTok(this.lastInputTokens)}/${fmtTok(this.contextWindow)} (${pctInt}%)${warnSuffix}`;
|
|
570
|
-
const BAR_W = Math.max(8, width - label.length - suffix.length - 2);
|
|
571
|
-
const filled = Math.round(pct * BAR_W);
|
|
572
|
-
// Context fill is the headline metric, render it with the shared meter
|
|
573
|
-
// primitive so the bar glyphs and width handling match every other panel.
|
|
574
|
-
lines.push(buildMeterLine(width, filled, BAR_W,
|
|
575
|
-
{ filled: barColor, empty: C.barBg, label: C.label },
|
|
576
|
-
{ prefix: label, suffix },
|
|
577
|
-
));
|
|
578
|
-
|
|
579
|
-
// Pressure pill, absorbed from ContextVisualizerPanel's headline indicator.
|
|
580
|
-
const overLimit = this.contextWindow > 0 && this.lastInputTokens > this.contextWindow;
|
|
581
|
-
const pressureState = overLimit || pct >= WARN_RED ? 'bad' : pct >= WARN_YELLOW ? 'warn' : 'good';
|
|
582
|
-
const pressureLabel = overLimit ? 'over limit' : pct >= WARN_RED ? 'critical' : pct >= WARN_YELLOW ? 'elevated' : 'healthy';
|
|
583
|
-
lines.push(buildStyledPanelLine(width, [
|
|
584
|
-
{ text: ' ', fg: C.dim },
|
|
585
|
-
...buildStatusPill(pressureState, pressureLabel),
|
|
586
|
-
]));
|
|
587
|
-
|
|
588
|
-
return lines;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/** Session cumulative totals with color-coded labels, plus an inline cost estimate. */
|
|
592
|
-
private renderTotals(width: number): Line[] {
|
|
593
|
-
const lines: Line[] = [];
|
|
594
|
-
lines.push(this.paintTextLine(' Session Totals:', width, C.label));
|
|
595
|
-
|
|
596
|
-
const u = this.sessionUsage;
|
|
597
|
-
const total = u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
598
|
-
|
|
599
|
-
const rows: Array<[string, number, string]> = [
|
|
600
|
-
[' Input: ', u.input, C.input],
|
|
601
|
-
[' Output: ', u.output, C.output],
|
|
602
|
-
[' Cache Read: ', u.cacheRead, C.cacheRead],
|
|
603
|
-
[' Cache Write:', u.cacheWrite, C.cacheWrite],
|
|
604
|
-
[' Total: ', total, C.value],
|
|
605
|
-
];
|
|
606
|
-
|
|
607
|
-
for (const [lbl, val, color] of rows) {
|
|
608
|
-
if (lines.length >= 8) break; // guard, don't overflow the section
|
|
609
|
-
lines.push(buildStyledPanelLine(width, [
|
|
610
|
-
{ text: lbl, fg: C.label },
|
|
611
|
-
{ text: fmtTok(val), fg: color, bold: lbl.includes('Total') },
|
|
612
|
-
]));
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Inline cost estimate, absorbed pricing wiring (cost-utils.ts), shown
|
|
616
|
-
// whenever the active model id is known.
|
|
617
|
-
if (this.getCurrentModelId) {
|
|
618
|
-
const modelId = this.getCurrentModelId();
|
|
619
|
-
const priced = isModelPriced(modelId);
|
|
620
|
-
const cost = calcSessionCost(u.input, u.output, u.cacheRead, u.cacheWrite, modelId);
|
|
621
|
-
// Dollars carry their source; an unknown price renders the explicit
|
|
622
|
-
// marker (never $0.00) and names the one-key fix on the cost panel.
|
|
623
|
-
const source = priced ? describePricingSource(modelId) : null;
|
|
624
|
-
lines.push(buildStyledPanelLine(width, [
|
|
625
|
-
{ text: ' Cost: ', fg: C.label },
|
|
626
|
-
{ text: priced ? `$${cost.toFixed(4)}` : 'price unknown', fg: C.value, bold: true },
|
|
627
|
-
...(priced && source
|
|
628
|
-
? [{ text: ` (${source})`, fg: C.dim }]
|
|
629
|
-
: priced
|
|
630
|
-
? []
|
|
631
|
-
: [{ text: ': set it with p on the cost panel', fg: C.dim }]),
|
|
632
|
-
]));
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return lines;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/** Last N turns table: turn#, in, out, CR, CW, total. */
|
|
639
|
-
private renderTurnHistory(width: number, maxRows: number): Line[] {
|
|
640
|
-
const available = Math.max(0, maxRows - 1); // minus col header
|
|
641
|
-
const toShow = this.turnHistory.slice(-Math.max(0, available));
|
|
642
|
-
|
|
643
|
-
// Width-aware aligned table via the shared primitive (replaces manual
|
|
644
|
-
// per-cell column math, which miscounts on wide glyphs).
|
|
645
|
-
return buildTable(
|
|
646
|
-
width,
|
|
647
|
-
[
|
|
648
|
-
{ label: '#', width: 4, align: 'right' },
|
|
649
|
-
{ label: 'Input', width: 9, align: 'right' },
|
|
650
|
-
{ label: 'Output', width: 9, align: 'right' },
|
|
651
|
-
{ label: 'CR', width: 7, align: 'right' },
|
|
652
|
-
{ label: 'CW', width: 7, align: 'right' },
|
|
653
|
-
{ label: 'Total', align: 'right' },
|
|
654
|
-
],
|
|
655
|
-
toShow.map((t, i) => {
|
|
656
|
-
const turnNum = this.turnHistory.length - toShow.length + i + 1;
|
|
657
|
-
const total = t.input + t.output + t.cacheRead + t.cacheWrite;
|
|
658
|
-
return {
|
|
659
|
-
cells: [
|
|
660
|
-
{ text: String(turnNum), fg: C.dim },
|
|
661
|
-
{ text: fmtTok(t.input), fg: C.input },
|
|
662
|
-
{ text: fmtTok(t.output), fg: C.output },
|
|
663
|
-
{ text: fmtTok(t.cacheRead), fg: C.cacheRead },
|
|
664
|
-
{ text: fmtTok(t.cacheWrite), fg: C.cacheWrite },
|
|
665
|
-
{ text: fmtTok(total), fg: C.value },
|
|
666
|
-
],
|
|
667
|
-
};
|
|
668
|
-
}),
|
|
669
|
-
{ ...DEFAULT_PANEL_PALETTE, label: C.turnHeader },
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/** Paint a plain text string into a new Line. */
|
|
674
|
-
private paintTextLine(
|
|
675
|
-
text: string,
|
|
676
|
-
width: number,
|
|
677
|
-
fg: string,
|
|
678
|
-
opts: { bold?: boolean; dim?: boolean } = {},
|
|
679
|
-
): Line {
|
|
680
|
-
const line = createEmptyLine(width);
|
|
681
|
-
let x = 0;
|
|
682
|
-
for (const ch of text) {
|
|
683
|
-
if (x >= width) break;
|
|
684
|
-
line[x++] = createStyledCell(ch, { fg, bold: opts.bold, dim: opts.dim });
|
|
685
|
-
}
|
|
686
|
-
return line;
|
|
687
|
-
}
|
|
688
|
-
}
|