@pellux/goodvibes-tui 2.0.21 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -1
- package/README.md +30 -29
- package/docs/README.md +8 -5
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +28 -19
- 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-reference.ts +2 -2
- 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/live-verifier.ts +0 -10
- package/src/version.ts +1 -1
- 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/verification/verification-ledger.ts +0 -707
- /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,35 +1,18 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// ui-primitives.ts, glyph registry +
|
|
2
|
+
// ui-primitives.ts, glyph registry + spinner frames.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// (@pellux/goodvibes-sdk/platform/presentation, already
|
|
7
|
-
// adopted by the agent), consumed here so the TUI and the agent
|
|
8
|
-
// share ONE source (Mike's move-to-SDK ruling, machinery needed by 2+
|
|
9
|
-
// surfaces => SDK). The TUI was the reference these values were lifted from
|
|
10
|
-
// verbatim, so this swap is byte-identical. See
|
|
11
|
-
// docs/decisions/2026-07-05-presentation-contract-sdk-extraction.md in the SDK.
|
|
4
|
+
// GLYPHS and SPINNER_FRAMES are the SDK presentation contract
|
|
5
|
+
// (@pellux/goodvibes-sdk/platform/presentation), shared with the agent.
|
|
12
6
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// this dark constant unchanged.
|
|
7
|
+
// Colours no longer live here: the static dark UI_TONES / DIFF_TONES tables
|
|
8
|
+
// were retired when the TUI adopted the SDK theme engine. Every colour is read
|
|
9
|
+
// from the active theme in theme.ts (activeTokens / activeUiTones /
|
|
10
|
+
// activeDiffTones / activeTheme).
|
|
18
11
|
// ---------------------------------------------------------------------------
|
|
19
12
|
|
|
20
13
|
import {
|
|
21
14
|
GLYPHS,
|
|
22
|
-
TONE_TOKENS,
|
|
23
|
-
DIFF_TONES,
|
|
24
15
|
SPINNER_FRAMES,
|
|
25
16
|
} from '@pellux/goodvibes-sdk/platform/presentation';
|
|
26
17
|
|
|
27
|
-
export { GLYPHS,
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* UI_TONES, the single chrome/color-token source for src/renderer and
|
|
31
|
-
* src/panels. Concrete (dark-mode) values; resolveUiTones(mode) in theme.ts
|
|
32
|
-
* is the single mode-resolved read path, this object is the 'dark' entry
|
|
33
|
-
* (== resolveTones('dark') from the SDK presentation contract).
|
|
34
|
-
*/
|
|
35
|
-
export const UI_TONES = TONE_TOKENS;
|
|
18
|
+
export { GLYPHS, SPINNER_FRAMES };
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderUsageModal, context and cost for this session (replaces the Tokens and
|
|
3
|
+
* Cost views).
|
|
4
|
+
*
|
|
5
|
+
* ✦ Usage this session esc
|
|
6
|
+
*
|
|
7
|
+
* Overview Turns Agents
|
|
8
|
+
*
|
|
9
|
+
* Context 13.2K of 1.0M · 1%
|
|
10
|
+
* ██──────────────────────────────────────────────┃──────────────────
|
|
11
|
+
* compacts at 80% ┘
|
|
12
|
+
*
|
|
13
|
+
* Session Per turn last 8
|
|
14
|
+
* Input 53.0K ██
|
|
15
|
+
* ... ■ input ■ output ■ cache read
|
|
16
|
+
*
|
|
17
|
+
* Overview carries the context bar (with the configured compaction threshold
|
|
18
|
+
* marked), the session totals, the per-turn bars and the fleet split. Turns is
|
|
19
|
+
* the per-turn table (cache writes included); Agents is the per-agent cost
|
|
20
|
+
* ledger with the plan total. Budget and price entry replace the tab content
|
|
21
|
+
* while they are open.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { mixHex } from '@pellux/goodvibes-sdk/platform/presentation';
|
|
25
|
+
import { activeTokens } from './theme.ts';
|
|
26
|
+
import { beginModal, finishModal, clipText, scrollCountText, type KitHint, type ModalFrame, type SurfaceLayer } from './surface-kit.ts';
|
|
27
|
+
import { drawList, type KitRow } from './surface-kit-list.ts';
|
|
28
|
+
import { drawTabRows, drawTextBlock, tabRowCount } from './surface-kit-extra.ts';
|
|
29
|
+
import { abbreviateCount } from '../utils/format-number.ts';
|
|
30
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
31
|
+
import type { UsageAgentEntry, UsageTracker, UsageTurn } from '../runtime/usage-tracker.ts';
|
|
32
|
+
|
|
33
|
+
export type UsageTab = 'overview' | 'turns' | 'agents';
|
|
34
|
+
|
|
35
|
+
export const USAGE_TABS: readonly UsageTab[] = ['overview', 'turns', 'agents'];
|
|
36
|
+
|
|
37
|
+
const TAB_LABELS: Readonly<Record<UsageTab, string>> = { overview: 'Overview', turns: 'Turns', agents: 'Agents' };
|
|
38
|
+
|
|
39
|
+
/** What the renderer reads from the modal. */
|
|
40
|
+
export interface UsageModalView {
|
|
41
|
+
readonly tracker: UsageTracker;
|
|
42
|
+
readonly tab: UsageTab;
|
|
43
|
+
/** Turns hidden at the newest end (0 = showing the latest). */
|
|
44
|
+
readonly turnOffset: number;
|
|
45
|
+
/** Selected agent in the Agents tab. */
|
|
46
|
+
readonly agentIndex: number;
|
|
47
|
+
/** An open budget or price entry, or null. */
|
|
48
|
+
readonly entry: { readonly kind: 'budget' | 'price'; readonly draft: string } | null;
|
|
49
|
+
/** A one-line result of the last action, or null. */
|
|
50
|
+
readonly status: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** '53.0K', '1.2M'. */
|
|
54
|
+
function formatTokenCount(n: number): string {
|
|
55
|
+
return abbreviateCount(Math.max(0, Math.round(n))).replace(/k$/, 'K');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** '$0.246', '<$0.0001', '$0.00'. */
|
|
59
|
+
export function formatUsd(usd: number): string {
|
|
60
|
+
if (usd === 0) return '$0.00';
|
|
61
|
+
if (usd < 0.0001) return '<$0.0001';
|
|
62
|
+
if (usd < 0.01) return `$${usd.toFixed(4)}`;
|
|
63
|
+
return `$${usd.toFixed(3)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hintsFor(view: UsageModalView): KitHint[] {
|
|
67
|
+
if (view.entry) return [['⏎', 'save'], ['esc', 'cancel']];
|
|
68
|
+
const base: KitHint[] = [['c', 'compact now']];
|
|
69
|
+
if (view.tab === 'agents') base.push(['↑↓', 'select agent']);
|
|
70
|
+
else base.push(['↑↓', 'scroll turns']);
|
|
71
|
+
base.push(['tab', 'next view'], ['b', 'budget'], ['p', 'model price'], ['r', 'refresh']);
|
|
72
|
+
return base;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Overview
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
function drawContext(f: ModalFrame, view: UsageModalView, y: number): number {
|
|
80
|
+
const t = activeTokens();
|
|
81
|
+
const { canvas, l, r } = f;
|
|
82
|
+
const used = view.tracker.contextTokens();
|
|
83
|
+
const window = view.tracker.contextWindow();
|
|
84
|
+
canvas.put(l, y, 'Context', { fg: t.accent, bold: true });
|
|
85
|
+
const pct = window > 0 ? Math.min(100, Math.round((used / window) * 100)) : 0;
|
|
86
|
+
const right = window > 0 ? `${formatTokenCount(used)} of ${formatTokenCount(window)} · ${pct}%` : 'context window unknown';
|
|
87
|
+
if (getDisplayWidth(right) < r - l - 9) canvas.right(r, y, right, { fg: t.textMuted });
|
|
88
|
+
if (window <= 0) return y + 2;
|
|
89
|
+
|
|
90
|
+
const barY = y + 2;
|
|
91
|
+
const width = r - l + 1;
|
|
92
|
+
const fraction = Math.min(1, used / window);
|
|
93
|
+
const threshold = view.tracker.compactThreshold();
|
|
94
|
+
const filled = used > 0 ? Math.max(1, Math.round(width * fraction)) : 0;
|
|
95
|
+
const hot = threshold > 0 && fraction >= threshold ? t.error : fraction >= 0.65 ? t.warning : null;
|
|
96
|
+
const tick = threshold > 0 ? Math.min(width - 1, Math.round(width * threshold)) : -1;
|
|
97
|
+
for (let q = 0; q < width; q++) {
|
|
98
|
+
if (q === tick) { canvas.put(l + q, barY, '┃', { fg: t.warning }); continue; }
|
|
99
|
+
const on = q < filled;
|
|
100
|
+
const fg = on ? (hot ?? (width > 1 ? mixHex(t.brand, t.brandEnd, q / (width - 1)) : t.brand)) : t.border;
|
|
101
|
+
canvas.put(l + q, barY, on ? '█' : '─', { fg });
|
|
102
|
+
}
|
|
103
|
+
const labelY = barY + 1;
|
|
104
|
+
if (tick >= 0) {
|
|
105
|
+
const label = `compacts at ${Math.round(threshold * 100)}% ┘`;
|
|
106
|
+
const end = l + tick;
|
|
107
|
+
const start = Math.max(l, end - getDisplayWidth(label) + 1);
|
|
108
|
+
canvas.put(start, labelY, clipText(label, end - start + 1), { fg: t.textFaint });
|
|
109
|
+
} else {
|
|
110
|
+
canvas.put(l, labelY, 'auto-compaction is off', { fg: t.textFaint });
|
|
111
|
+
}
|
|
112
|
+
return labelY + 2;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function drawSessionTable(f: ModalFrame, view: UsageModalView, x: number, xr: number, y: number, maxY: number): number {
|
|
116
|
+
const t = activeTokens();
|
|
117
|
+
const { canvas } = f;
|
|
118
|
+
const u = view.tracker.totals();
|
|
119
|
+
const cost = view.tracker.sessionCost();
|
|
120
|
+
if (y > maxY) return y;
|
|
121
|
+
canvas.put(x, y, 'Session', { fg: t.accent, bold: true });
|
|
122
|
+
let yy = y + 1;
|
|
123
|
+
const rows: Array<[string, string, boolean, string?]> = [
|
|
124
|
+
['Input', formatTokenCount(u.input), false],
|
|
125
|
+
['Output', formatTokenCount(u.output), false],
|
|
126
|
+
['Cache read', formatTokenCount(u.cacheRead), false],
|
|
127
|
+
['Cache write', formatTokenCount(u.cacheWrite), false],
|
|
128
|
+
['Total', formatTokenCount(u.input + u.output + u.cacheRead + u.cacheWrite), false],
|
|
129
|
+
['Cost', cost.priced ? formatUsd(cost.usd) : 'price unknown', true, view.tracker.overBudget() ? t.error : undefined],
|
|
130
|
+
];
|
|
131
|
+
for (const [label, value, bold, fg] of rows) {
|
|
132
|
+
if (yy > maxY) return yy;
|
|
133
|
+
canvas.put(x, yy, label, { fg: t.textMuted });
|
|
134
|
+
canvas.right(xr, yy, value, { fg: fg ?? t.text, bold });
|
|
135
|
+
yy++;
|
|
136
|
+
}
|
|
137
|
+
const budget = view.tracker.budget();
|
|
138
|
+
if (yy <= maxY) {
|
|
139
|
+
canvas.put(x, yy, 'Budget', { fg: t.textMuted });
|
|
140
|
+
const text = budget > 0 ? `${formatUsd(budget)} · ${Math.round((cost.usd / budget) * 100)}%` : 'none';
|
|
141
|
+
canvas.right(xr, yy, text, { fg: view.tracker.overBudget() ? t.error : budget > 0 ? t.text : t.textFaint });
|
|
142
|
+
yy++;
|
|
143
|
+
}
|
|
144
|
+
const note = !cost.priced ? 'no known price: press p to set one' : cost.source;
|
|
145
|
+
if (note && yy <= maxY) yy = drawTextBlock(canvas, x, yy, xr - x + 1, [{ text: note, style: { fg: t.textFaint } }], maxY);
|
|
146
|
+
if (view.tracker.hasUnpricedSpend() && budget > 0 && yy <= maxY) {
|
|
147
|
+
yy = drawTextBlock(canvas, x, yy, xr - x + 1, [{ text: 'some spend has no known price and is not counted', style: { fg: t.warning } }], maxY);
|
|
148
|
+
}
|
|
149
|
+
return yy;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function drawFleetSplit(f: ModalFrame, view: UsageModalView, x: number, xr: number, y: number, maxY: number): number {
|
|
153
|
+
const t = activeTokens();
|
|
154
|
+
if (y + 1 > maxY) return y;
|
|
155
|
+
const you = view.tracker.sessionCost();
|
|
156
|
+
const fleet = view.tracker.fleetCost();
|
|
157
|
+
f.canvas.put(x, y, 'Fleet', { fg: t.accent, bold: true });
|
|
158
|
+
const text = `you ${you.priced ? formatUsd(you.usd) : 'price unknown'} · agents ${fleet === null ? 'none priced' : formatUsd(fleet)}`;
|
|
159
|
+
return drawTextBlock(f.canvas, x, y + 1, xr - x + 1, [{ text, style: { fg: t.textMuted } }], maxY);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The per-turn stacked bars (cache read, input, output from the bottom). Returns the row after the legend. */
|
|
163
|
+
function drawTurnBars(f: ModalFrame, view: UsageModalView, x: number, xr: number, y: number, maxY: number): number {
|
|
164
|
+
const t = activeTokens();
|
|
165
|
+
const { canvas } = f;
|
|
166
|
+
const turns = view.tracker.turns();
|
|
167
|
+
canvas.put(x, y, 'Per turn', { fg: t.accent, bold: true });
|
|
168
|
+
if (turns.length === 0) {
|
|
169
|
+
return drawTextBlock(canvas, x, y + 2, xr - x + 1, [{ text: 'Token bars appear here after the first completed turn.', style: { fg: t.textMuted } }], maxY);
|
|
170
|
+
}
|
|
171
|
+
const stride = 4;
|
|
172
|
+
const slots = Math.max(1, Math.floor((xr - x + 3) / stride));
|
|
173
|
+
const end = Math.max(0, turns.length - view.turnOffset);
|
|
174
|
+
const start = Math.max(0, end - slots);
|
|
175
|
+
const shown = turns.slice(start, end);
|
|
176
|
+
const countText = view.turnOffset > 0 ? `turns ${start + 1}-${end} of ${turns.length}` : `last ${shown.length}`;
|
|
177
|
+
if (getDisplayWidth(countText) < xr - x - 10) canvas.right(xr, y, countText, { fg: t.textFaint });
|
|
178
|
+
|
|
179
|
+
// Rows: header, bars, a number row, blank, legend.
|
|
180
|
+
const legendY = maxY;
|
|
181
|
+
const numbersY = legendY - 2;
|
|
182
|
+
const barTop = y + 1;
|
|
183
|
+
const barBottom = numbersY - 1;
|
|
184
|
+
const barRows = barBottom - barTop + 1;
|
|
185
|
+
if (barRows < 2) return y + 1;
|
|
186
|
+
const totalOf = (turn: UsageTurn): number => turn.input + turn.output + turn.cacheRead;
|
|
187
|
+
const max = Math.max(1, ...shown.map(totalOf));
|
|
188
|
+
shown.forEach((turn, k) => {
|
|
189
|
+
const bx = x + k * stride;
|
|
190
|
+
const scale = barRows / max;
|
|
191
|
+
const parts: Array<[number, string]> = [[turn.cacheRead, t.info], [turn.input, t.brand], [turn.output, t.brandEnd]];
|
|
192
|
+
let heights = parts.map(([n]) => Math.round(n * scale));
|
|
193
|
+
if (heights.every((h) => h === 0) && totalOf(turn) > 0) {
|
|
194
|
+
const largest = parts.reduce((best, part, i) => (part[0] > parts[best]![0] ? i : best), 0);
|
|
195
|
+
heights = heights.map((h, i) => (i === largest ? 1 : h));
|
|
196
|
+
}
|
|
197
|
+
let row = barBottom;
|
|
198
|
+
parts.forEach(([, color], i) => {
|
|
199
|
+
for (let h = 0; h < heights[i]! && row >= barTop; h++, row--) canvas.put(bx, row, '██', { fg: color });
|
|
200
|
+
});
|
|
201
|
+
canvas.put(bx, numbersY, String(start + k + 1).slice(-3), { fg: t.textFaint });
|
|
202
|
+
});
|
|
203
|
+
let lx = x;
|
|
204
|
+
for (const [name, color] of [['input', t.brand], ['output', t.brandEnd], ['cache read', t.info]] as const) {
|
|
205
|
+
if (lx + 2 + getDisplayWidth(name) > xr) break;
|
|
206
|
+
canvas.put(lx, legendY, '■', { fg: color });
|
|
207
|
+
lx = canvas.put(lx + 2, legendY, name, { fg: t.textMuted }) + 3;
|
|
208
|
+
}
|
|
209
|
+
return legendY + 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function drawOverview(f: ModalFrame, view: UsageModalView, y: number): void {
|
|
213
|
+
const t = activeTokens();
|
|
214
|
+
const { l, r } = f;
|
|
215
|
+
const bottom = view.status ? f.bottom - 2 : f.bottom;
|
|
216
|
+
let yy = drawContext(f, view, y);
|
|
217
|
+
const inner = r - l + 1;
|
|
218
|
+
if (inner >= 60) {
|
|
219
|
+
const leftW = Math.min(34, Math.floor(inner * 0.4));
|
|
220
|
+
const leftR = l + leftW - 1;
|
|
221
|
+
const gx = leftR + 5;
|
|
222
|
+
const leftEnd = drawSessionTable(f, view, l, leftR, yy, bottom);
|
|
223
|
+
drawFleetSplit(f, view, l, leftR, leftEnd + 1, bottom);
|
|
224
|
+
if (bottom - yy >= 5) drawTurnBars(f, view, gx, r, yy, bottom);
|
|
225
|
+
} else {
|
|
226
|
+
yy = drawSessionTable(f, view, l, r, yy, bottom);
|
|
227
|
+
yy = drawFleetSplit(f, view, l, r, yy + 1, bottom);
|
|
228
|
+
}
|
|
229
|
+
if (view.status) f.canvas.put(l, f.bottom, clipText(view.status, inner), { fg: t.textMuted });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Turns and agents
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
function turnRows(view: UsageModalView, width: number): KitRow[] {
|
|
237
|
+
const turns = view.tracker.turns();
|
|
238
|
+
const rows: KitRow[] = [];
|
|
239
|
+
const selected = Math.max(0, turns.length - 1 - view.turnOffset);
|
|
240
|
+
turns.forEach((turn, i) => {
|
|
241
|
+
const total = turn.input + turn.output + turn.cacheRead + turn.cacheWrite;
|
|
242
|
+
const cells = [turn.input, turn.output, turn.cacheRead, turn.cacheWrite].map(formatTokenCount);
|
|
243
|
+
const detail = width >= 60
|
|
244
|
+
? `in ${cells[0]} out ${cells[1]} cache read ${cells[2]} cache write ${cells[3]}`
|
|
245
|
+
: `in ${cells[0]} out ${cells[1]}`;
|
|
246
|
+
rows.push({ label: `Turn ${i + 1}`, desc: detail, right: formatTokenCount(total), selected: i === selected });
|
|
247
|
+
});
|
|
248
|
+
return rows;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function drawTurns(f: ModalFrame, view: UsageModalView, y: number): void {
|
|
252
|
+
const t = activeTokens();
|
|
253
|
+
const turns = view.tracker.turns();
|
|
254
|
+
if (turns.length === 0) {
|
|
255
|
+
drawTextBlock(f.canvas, f.l, y, f.r - f.l + 1, [{ text: 'No turns recorded yet. Each completed turn adds a row with its input, output and cache tokens.', style: { fg: t.textMuted } }], f.bottom);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const result = drawList(f.canvas, { rows: turnRows(view, f.r - f.l + 1), top: y, bottom: f.bottom, x0: f.l, x1: f.r, scrollKey: { owner: view.tracker, name: 'turns' } });
|
|
259
|
+
f.hintRight = scrollCountText(result.above, result.below);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function agentMark(agent: UsageAgentEntry): { mark: string; fg: string } {
|
|
263
|
+
const t = activeTokens();
|
|
264
|
+
if (agent.status === 'running') return { mark: '◐', fg: t.brand };
|
|
265
|
+
if (agent.status === 'failed') return { mark: '✕', fg: t.error };
|
|
266
|
+
return { mark: '✓', fg: t.success };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function drawAgents(f: ModalFrame, view: UsageModalView, y: number): void {
|
|
270
|
+
const t = activeTokens();
|
|
271
|
+
const agents = view.tracker.agents();
|
|
272
|
+
const session = view.tracker.sessionCost();
|
|
273
|
+
const planCost = agents.reduce((sum, a) => sum + a.cost, 0) + session.usd;
|
|
274
|
+
const running = agents.filter((a) => a.status === 'running').length;
|
|
275
|
+
const failed = agents.filter((a) => a.status === 'failed').length;
|
|
276
|
+
const parts = [`plan total ${session.priced ? formatUsd(planCost) : 'price unknown'}`, `${agents.length} agent${agents.length === 1 ? '' : 's'}`];
|
|
277
|
+
if (running > 0) parts.push(`${running} running`);
|
|
278
|
+
if (failed > 0) parts.push(`${failed} failed`);
|
|
279
|
+
const yy = drawTextBlock(f.canvas, f.l, y, f.r - f.l + 1, [{ text: parts.join(' · '), style: { fg: t.textMuted } }], f.bottom);
|
|
280
|
+
if (agents.length === 0) {
|
|
281
|
+
drawTextBlock(f.canvas, f.l, yy + 1, f.r - f.l + 1, [{ text: 'No agents spawned this session. Agent costs appear here once delegated or background agents start.', style: { fg: t.textMuted } }], f.bottom);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const selected = Math.max(0, Math.min(view.agentIndex, agents.length - 1));
|
|
285
|
+
const rows: KitRow[] = agents.map((agent, i) => {
|
|
286
|
+
const { mark, fg } = agentMark(agent);
|
|
287
|
+
const tokens = agent.inputTokens > 0 ? `${formatTokenCount(agent.inputTokens)} in · ${formatTokenCount(agent.outputTokens)} out` : 'no usage yet';
|
|
288
|
+
const cost = agent.inputTokens > 0 && agent.cost === 0 && agent.model === 'unknown' ? 'unpriced' : agent.cost > 0 ? formatUsd(agent.cost) : '-';
|
|
289
|
+
return {
|
|
290
|
+
label: agent.agentId.slice(0, 8),
|
|
291
|
+
desc: `${agent.task} · ${agent.model} · ${tokens}`,
|
|
292
|
+
right: cost,
|
|
293
|
+
mark,
|
|
294
|
+
markFg: fg,
|
|
295
|
+
selected: i === selected,
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
const result = drawList(f.canvas, { rows, top: yy + 1, bottom: f.bottom, x0: f.l, x1: f.r, scrollKey: { owner: view.tracker, name: 'agents' } });
|
|
299
|
+
f.hintRight = scrollCountText(result.above, result.below);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Entry
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
function drawEntry(f: ModalFrame, view: UsageModalView, y: number): void {
|
|
307
|
+
const t = activeTokens();
|
|
308
|
+
const entry = view.entry!;
|
|
309
|
+
const width = f.r - f.l + 1;
|
|
310
|
+
let yy = y;
|
|
311
|
+
if (entry.kind === 'budget') {
|
|
312
|
+
yy = drawTextBlock(f.canvas, f.l, yy, width, [
|
|
313
|
+
{ text: 'Budget alert', style: { fg: t.accent, bold: true } },
|
|
314
|
+
{ text: 'Type a USD amount. You are alerted when this session costs more; 0 turns the alert off. The same setting as /cost budget <usd>.', style: { fg: t.textMuted } },
|
|
315
|
+
], f.bottom);
|
|
316
|
+
yy++;
|
|
317
|
+
const end = f.canvas.put(f.l, yy, `$${entry.draft}`, { fg: t.text });
|
|
318
|
+
f.canvas.put(end, yy, '▏', { fg: t.brand });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const model = view.tracker.modelId();
|
|
322
|
+
if (!view.tracker.canSetModelPrice()) {
|
|
323
|
+
drawTextBlock(f.canvas, f.l, yy, width, [
|
|
324
|
+
{ text: 'Model price', style: { fg: t.accent, bold: true } },
|
|
325
|
+
{ text: `The current model (${model}) has no provider prefix, so a price cannot be stored for it: prices are keyed provider:model.`, style: { fg: t.textMuted } },
|
|
326
|
+
], f.bottom);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
yy = drawTextBlock(f.canvas, f.l, yy, width, [
|
|
330
|
+
{ text: `Model price for ${model}`, style: { fg: t.accent, bold: true } },
|
|
331
|
+
{ text: 'Your price, in USD per 1M tokens, as input,output (for example 3.00,15.00). It wins over provider and catalog prices everywhere.', style: { fg: t.textMuted } },
|
|
332
|
+
], f.bottom);
|
|
333
|
+
yy++;
|
|
334
|
+
const end = f.canvas.put(f.l, yy, entry.draft, { fg: t.text });
|
|
335
|
+
f.canvas.put(end, yy, '▏', { fg: t.brand });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// Frame
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
|
|
342
|
+
export function renderUsageModal(view: UsageModalView, screenWidth: number, screenHeight: number): SurfaceLayer {
|
|
343
|
+
const f = beginModal(screenWidth, screenHeight, { title: 'Usage', sub: 'this session', hints: hintsFor(view) });
|
|
344
|
+
const labels = USAGE_TABS.map((tab) => TAB_LABELS[tab]);
|
|
345
|
+
const tabRows = view.entry ? 0 : tabRowCount(labels, f.l + 1, f.r - 1);
|
|
346
|
+
if (tabRows > 0) drawTabRows(f.canvas, f.l + 1, f.top, labels, USAGE_TABS.indexOf(view.tab), f.r - 1);
|
|
347
|
+
const y = f.top + (tabRows > 0 ? tabRows + 1 : 0);
|
|
348
|
+
if (view.entry) drawEntry(f, view, y);
|
|
349
|
+
else if (view.tab === 'turns') drawTurns(f, view, y);
|
|
350
|
+
else if (view.tab === 'agents') drawAgents(f, view, y);
|
|
351
|
+
else drawOverview(f, view, y);
|
|
352
|
+
return finishModal(f);
|
|
353
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* user-message.ts, a user message in the transcript.
|
|
3
|
+
*
|
|
4
|
+
* col 2 ┃ in the bar color on every row, padding rows included
|
|
5
|
+
* cols 3..w-3 the surface fill, full width
|
|
6
|
+
* rows one padding row, the text, one padding row
|
|
7
|
+
* text from column 5, wrapped to width-9
|
|
8
|
+
*
|
|
9
|
+
* The composer is drawn the same way (composer.ts), so what you typed and
|
|
10
|
+
* what you sent look alike.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { type Line, createEmptyLine } from '@pellux/goodvibes-sdk/platform/types';
|
|
14
|
+
import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
|
|
15
|
+
|
|
16
|
+
const USER_MESSAGE_BAR_X = 2;
|
|
17
|
+
const USER_MESSAGE_FILL_X = 3;
|
|
18
|
+
const USER_MESSAGE_TEXT_X = 5;
|
|
19
|
+
|
|
20
|
+
export interface UserMessageStyle {
|
|
21
|
+
readonly bar: string;
|
|
22
|
+
readonly bg: string;
|
|
23
|
+
readonly text: string;
|
|
24
|
+
readonly strikethrough?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function row(width: number, style: UserMessageStyle): Line {
|
|
28
|
+
const line = createEmptyLine(width);
|
|
29
|
+
for (const cell of line) cell.bg = '';
|
|
30
|
+
if (width > USER_MESSAGE_BAR_X) {
|
|
31
|
+
line[USER_MESSAGE_BAR_X] = { char: '┃', fg: style.bar, bg: '', bold: false, dim: false, underline: false, italic: false, strikethrough: false };
|
|
32
|
+
}
|
|
33
|
+
for (let x = USER_MESSAGE_FILL_X; x <= width - 3; x++) {
|
|
34
|
+
line[x] = { char: ' ', fg: style.text, bg: style.bg, bold: false, dim: false, underline: false, italic: false, strikethrough: false };
|
|
35
|
+
}
|
|
36
|
+
return line;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function renderUserMessage(text: string, width: number, style: UserMessageStyle): Line[] {
|
|
40
|
+
const textWidth = Math.max(1, width - 9);
|
|
41
|
+
const wrapped = wrapText(text, textWidth);
|
|
42
|
+
const lines: Line[] = [row(width, style)];
|
|
43
|
+
for (const content of wrapped.length > 0 ? wrapped : ['']) {
|
|
44
|
+
const line = row(width, style);
|
|
45
|
+
let x = USER_MESSAGE_TEXT_X;
|
|
46
|
+
for (const ch of content) {
|
|
47
|
+
const w = getDisplayWidth(ch);
|
|
48
|
+
if (w <= 0) continue;
|
|
49
|
+
if (x + w > width - 4) break;
|
|
50
|
+
line[x] = { char: ch, fg: style.text, bg: style.bg, bold: false, dim: false, underline: false, italic: false, strikethrough: style.strikethrough ?? false };
|
|
51
|
+
if (w === 2) line[x + 1] = { ...line[x]!, char: '' };
|
|
52
|
+
x += w;
|
|
53
|
+
}
|
|
54
|
+
lines.push(line);
|
|
55
|
+
}
|
|
56
|
+
lines.push(row(width, style));
|
|
57
|
+
return lines;
|
|
58
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* voice-capture-chip.ts, the live-microphone chip at the left end of the status line.
|
|
3
|
+
*
|
|
4
|
+
* A capture device held open with nothing on screen saying so is the one
|
|
5
|
+
* state a voice feature must never be in, so the chip is never dropped for
|
|
6
|
+
* lack of room. It is short on purpose; the full wording (device, restart
|
|
7
|
+
* delay, latch reason) is in /voice status.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { StatusChip } from './status-line.ts';
|
|
11
|
+
import type { VoiceCaptureIndicatorState } from '../core/voice-capture-status.ts';
|
|
12
|
+
import { GLYPHS } from './ui-primitives.ts';
|
|
13
|
+
import { activeTokens, activeUiTones } from './theme.ts';
|
|
14
|
+
|
|
15
|
+
const VOICE_CHIP_LABELS: Record<VoiceCaptureIndicatorState['kind'], string> = {
|
|
16
|
+
requesting: 'mic opening',
|
|
17
|
+
recording: 'mic recording',
|
|
18
|
+
transcribing: 'transcribing',
|
|
19
|
+
'wake-listening': 'mic listening',
|
|
20
|
+
'wake-capturing': 'mic recording',
|
|
21
|
+
'wake-restarting': 'mic restarting',
|
|
22
|
+
'wake-latched': 'wake stopped',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const VOICE_CAPTURE_DESCRIPTIONS: Record<VoiceCaptureIndicatorState['kind'], string> = {
|
|
26
|
+
requesting: 'opening the microphone',
|
|
27
|
+
recording: 'recording: press the voice-input key again to stop',
|
|
28
|
+
transcribing: 'transcribing what you said',
|
|
29
|
+
'wake-listening': 'listening for the wake phrase',
|
|
30
|
+
'wake-capturing': 'wake heard: recording what follows',
|
|
31
|
+
'wake-restarting': 'capture stream ended: restarting',
|
|
32
|
+
'wake-latched': 'wake detection stopped',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** The full wording for /status: state, device and any detail (a restart delay, a latch reason). */
|
|
36
|
+
export function voiceCaptureDescription(state: VoiceCaptureIndicatorState): string {
|
|
37
|
+
const parts = [VOICE_CAPTURE_DESCRIPTIONS[state.kind]];
|
|
38
|
+
if (state.deviceLabel !== null) parts.push(state.deviceLabel);
|
|
39
|
+
if (state.detail !== undefined && state.detail.length > 0) parts.push(state.detail);
|
|
40
|
+
return parts.join(' · ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function voiceCaptureChip(state: VoiceCaptureIndicatorState): StatusChip {
|
|
44
|
+
const tones = activeUiTones();
|
|
45
|
+
const marker = state.kind === 'wake-latched' ? GLYPHS.status.blocked : GLYPHS.status.active;
|
|
46
|
+
const fg = state.kind === 'wake-latched' || state.kind === 'wake-restarting' ? tones.chrome.warn : tones.accent.control;
|
|
47
|
+
const text = `${marker} ${VOICE_CHIP_LABELS[state.kind]}`;
|
|
48
|
+
// voice.wake.indicator: banner draws the wake chip filled, statusline plain.
|
|
49
|
+
if (state.kind.startsWith('wake-') && state.indicator === 'banner') {
|
|
50
|
+
return { text: ` ${text} `, fg: activeTokens().backgroundElement, bg: fg, bold: true, keep: true };
|
|
51
|
+
}
|
|
52
|
+
return { text, fg, bold: true, keep: true };
|
|
53
|
+
}
|
|
@@ -5,7 +5,6 @@ import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
|
5
5
|
import type { MemorySpineClient } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
|
|
6
6
|
import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
7
7
|
import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
8
|
-
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
9
8
|
import type { ProviderApi } from '@pellux/goodvibes-sdk/platform/providers';
|
|
10
9
|
import type { OpsApi } from '@/runtime/index.ts';
|
|
11
10
|
import type { MutableRuntimeState } from '@/runtime/index.ts';
|
|
@@ -124,7 +123,6 @@ export type CreateBootstrapCommandContextOptions = {
|
|
|
124
123
|
mcpApi?: McpApi;
|
|
125
124
|
opsApi?: OpsApi;
|
|
126
125
|
directTransport?: DirectTransport;
|
|
127
|
-
panelManager: PanelManager;
|
|
128
126
|
worktreeRegistry: WorktreeRegistry;
|
|
129
127
|
sandboxSessionRegistry: SandboxSessionRegistry;
|
|
130
128
|
loadSystemPrompt: () => string;
|
|
@@ -220,7 +218,6 @@ export function createBootstrapCommandContext(
|
|
|
220
218
|
mcpApi,
|
|
221
219
|
opsApi,
|
|
222
220
|
directTransport,
|
|
223
|
-
panelManager,
|
|
224
221
|
worktreeRegistry,
|
|
225
222
|
sandboxSessionRegistry,
|
|
226
223
|
loadSystemPrompt,
|
|
@@ -287,7 +284,6 @@ export function createBootstrapCommandContext(
|
|
|
287
284
|
gatewayMethods,
|
|
288
285
|
workspaceTrustManager,
|
|
289
286
|
workspaceRegistrationManager,
|
|
290
|
-
panelManager,
|
|
291
287
|
profileManager,
|
|
292
288
|
bookmarkManager,
|
|
293
289
|
projectPlanningService,
|
|
@@ -317,7 +313,6 @@ export function createBootstrapCommandContext(
|
|
|
317
313
|
conversation,
|
|
318
314
|
runtime,
|
|
319
315
|
requestRender,
|
|
320
|
-
panelManager,
|
|
321
316
|
loadSystemPrompt,
|
|
322
317
|
activatePlan,
|
|
323
318
|
requestPermission,
|