@pellux/goodvibes-tui 2.0.21 → 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 +58 -0
- package/README.md +16 -16
- package/docs/README.md +4 -4
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- 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
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* throbber.ts, the one row above the input area that says what the main
|
|
3
|
+
* session is doing right now. It shows only while main is working (a turn
|
|
4
|
+
* runs, or the conversation is being compacted) and is gone at rest.
|
|
5
|
+
*
|
|
6
|
+
* col 3 the spinner (brand gradient on the glyph only)
|
|
7
|
+
* col 5..w-4 what is happening, then its elapsed time
|
|
8
|
+
*
|
|
9
|
+
* It is its own surface: never inside the input area, and never on the
|
|
10
|
+
* status line, which keeps session state (mode, cost, context bar) and the
|
|
11
|
+
* keys (esc interrupt).
|
|
12
|
+
*
|
|
13
|
+
* What it names, first match wins:
|
|
14
|
+
* - a permission prompt waiting on you: Waiting for your approval · bash · npm test · 12s
|
|
15
|
+
* - a compaction: Compacting the conversation · 4s
|
|
16
|
+
* - a running tool: Running bash · npm test · 3s
|
|
17
|
+
* - the model (the honest waiting phrase from the SDK): Thinking... · 12s · first token 1.2s · 40 tok/s
|
|
18
|
+
*
|
|
19
|
+
* Inside an agent or process view the row keeps main in sight the same way,
|
|
20
|
+
* led by "main ·" so it is never read as the view's own activity.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { type Line, createEmptyLine } from '@pellux/goodvibes-sdk/platform/types';
|
|
24
|
+
import type { ToolCall } from '@pellux/goodvibes-sdk/platform/types';
|
|
25
|
+
import { getDisplayWidth, interpolateColor, truncateDisplay } from '../utils/terminal-width.ts';
|
|
26
|
+
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
27
|
+
import { beadArgument, beadName } from './lane-graph/bead.ts';
|
|
28
|
+
import { activeTokens } from './theme.ts';
|
|
29
|
+
|
|
30
|
+
/** Column of the spinner (the input area's first fill column). */
|
|
31
|
+
const THROBBER_SPINNER_X = 3;
|
|
32
|
+
/** First text column (the input area's text column). */
|
|
33
|
+
const THROBBER_TEXT_X = 5;
|
|
34
|
+
/** The most a tool's argument takes before it is cut, so the elapsed time stays on the row. */
|
|
35
|
+
const ARGUMENT_MAX = 60;
|
|
36
|
+
const SEP = ' · ';
|
|
37
|
+
|
|
38
|
+
/** What the main session is doing, as the throbber names it. */
|
|
39
|
+
export type ThrobberActivity =
|
|
40
|
+
| { readonly kind: 'approval'; readonly tool: string; readonly argument: string; readonly elapsedMs?: number }
|
|
41
|
+
| { readonly kind: 'compacting'; readonly elapsedMs?: number }
|
|
42
|
+
| { readonly kind: 'tool'; readonly tool: string; readonly argument: string; readonly elapsedMs?: number }
|
|
43
|
+
| { readonly kind: 'model'; readonly phrase: string; readonly elapsedMs?: number; readonly ttftMs?: number; readonly tokenSpeed?: number };
|
|
44
|
+
|
|
45
|
+
/** One frame of the throbber. */
|
|
46
|
+
export interface ThrobberState {
|
|
47
|
+
/** The spinner glyph for this frame. */
|
|
48
|
+
readonly spinner: string;
|
|
49
|
+
/** Animation frame, used for the spinner's gradient position. */
|
|
50
|
+
readonly frame: number;
|
|
51
|
+
readonly activity: ThrobberActivity;
|
|
52
|
+
/** Set inside an agent or process view: whose activity this is ("main"). */
|
|
53
|
+
readonly owner?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A tool call as the throbber needs it: its name and arguments. */
|
|
57
|
+
export interface ThrobberToolCall {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly args?: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ThrobberActivityInput {
|
|
63
|
+
/** A main turn is running. */
|
|
64
|
+
readonly turnActive: boolean;
|
|
65
|
+
/** A compaction of the main conversation is running (inside a turn or not). */
|
|
66
|
+
readonly compacting: boolean;
|
|
67
|
+
/** When the running compaction started (epoch ms), once known. */
|
|
68
|
+
readonly compactingSinceMs?: number;
|
|
69
|
+
/** The permission prompt the turn is blocked on. */
|
|
70
|
+
readonly pendingApproval?: ThrobberToolCall | null;
|
|
71
|
+
/** The tool the turn is executing now, and when it started (epoch ms). */
|
|
72
|
+
readonly activeTool?: (ThrobberToolCall & { readonly startedAtMs?: number }) | null;
|
|
73
|
+
/** The model's honest waiting phrase (UIFactory.busyPhrase). */
|
|
74
|
+
readonly modelPhrase: string;
|
|
75
|
+
/** When the turn's current stream started (epoch ms); 0 or undefined when unknown. */
|
|
76
|
+
readonly turnStartMs?: number;
|
|
77
|
+
readonly ttftMs?: number;
|
|
78
|
+
readonly tokenSpeed?: number;
|
|
79
|
+
readonly now: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** "bash", "npm test": a call's short name and key argument, the way its bead names it. */
|
|
83
|
+
function describeToolCall(call: ThrobberToolCall): { tool: string; argument: string } {
|
|
84
|
+
const asCall = { id: '', name: call.name, arguments: call.args ?? {} } as ToolCall;
|
|
85
|
+
let argument = '';
|
|
86
|
+
try { argument = beadArgument(asCall); } catch { argument = ''; }
|
|
87
|
+
return { tool: beadName(asCall), argument: truncateDisplay(argument, ARGUMENT_MAX) };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** What main is doing now, or null at rest (the throbber is not drawn). */
|
|
91
|
+
export function resolveThrobberActivity(input: ThrobberActivityInput): ThrobberActivity | null {
|
|
92
|
+
if (!input.turnActive && !input.compacting) return null;
|
|
93
|
+
const since = (start: number | undefined): number | undefined => (start !== undefined && start > 0 ? Math.max(0, input.now - start) : undefined);
|
|
94
|
+
const turnElapsed = since(input.turnStartMs);
|
|
95
|
+
if (input.turnActive && input.pendingApproval) {
|
|
96
|
+
return { kind: 'approval', ...describeToolCall(input.pendingApproval), elapsedMs: turnElapsed };
|
|
97
|
+
}
|
|
98
|
+
if (input.compacting) return { kind: 'compacting', elapsedMs: since(input.compactingSinceMs) };
|
|
99
|
+
if (input.activeTool) {
|
|
100
|
+
return { kind: 'tool', ...describeToolCall(input.activeTool), elapsedMs: since(input.activeTool.startedAtMs) ?? turnElapsed };
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
kind: 'model',
|
|
104
|
+
phrase: input.modelPhrase,
|
|
105
|
+
elapsedMs: turnElapsed,
|
|
106
|
+
ttftMs: input.ttftMs !== undefined && input.ttftMs > 0 ? input.ttftMs : undefined,
|
|
107
|
+
tokenSpeed: input.tokenSpeed !== undefined && input.tokenSpeed > 0 ? input.tokenSpeed : undefined,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface ThrobberText {
|
|
112
|
+
readonly label: string;
|
|
113
|
+
readonly subject: string;
|
|
114
|
+
readonly tail: readonly string[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function textOf(activity: ThrobberActivity): ThrobberText {
|
|
118
|
+
const elapsed = activity.elapsedMs !== undefined ? [formatElapsed(activity.elapsedMs)] : [];
|
|
119
|
+
switch (activity.kind) {
|
|
120
|
+
case 'approval':
|
|
121
|
+
return { label: 'Waiting for your approval', subject: [activity.tool, activity.argument].filter(Boolean).join(SEP), tail: elapsed };
|
|
122
|
+
case 'compacting':
|
|
123
|
+
return { label: 'Compacting the conversation', subject: '', tail: elapsed };
|
|
124
|
+
case 'tool':
|
|
125
|
+
return { label: `Running ${activity.tool}`, subject: activity.argument, tail: elapsed };
|
|
126
|
+
case 'model': {
|
|
127
|
+
const tail = [...elapsed];
|
|
128
|
+
if (activity.ttftMs !== undefined) tail.push(`first token ${formatElapsed(activity.ttftMs)}`);
|
|
129
|
+
if (activity.tokenSpeed !== undefined) tail.push(`${Math.round(activity.tokenSpeed)} tok/s`);
|
|
130
|
+
return { label: activity.phrase, subject: '', tail };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function put(line: Line, x: number, endX: number, text: string, fg: string, bold = false): number {
|
|
136
|
+
let cx = x;
|
|
137
|
+
for (const ch of text) {
|
|
138
|
+
const w = getDisplayWidth(ch);
|
|
139
|
+
if (w <= 0) continue;
|
|
140
|
+
if (cx + w > endX) break;
|
|
141
|
+
line[cx] = { char: ch, fg, bg: '', bold, dim: false, underline: false, italic: false, strikethrough: false };
|
|
142
|
+
if (w === 2 && cx + 1 < line.length) line[cx + 1] = { ...line[cx]!, char: '' };
|
|
143
|
+
cx += w;
|
|
144
|
+
}
|
|
145
|
+
return cx;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Draw the throbber row. The text ends at width-4 or earlier. When the row is
|
|
150
|
+
* short the elapsed pieces go first, then the subject is cut, then the label.
|
|
151
|
+
*/
|
|
152
|
+
export function renderThrobberLine(width: number, state: ThrobberState): Line {
|
|
153
|
+
const t = activeTokens();
|
|
154
|
+
const line = createEmptyLine(width);
|
|
155
|
+
for (const cell of line) cell.bg = '';
|
|
156
|
+
const endX = Math.max(THROBBER_TEXT_X, width - 3); // exclusive: the last text column is width-4
|
|
157
|
+
if (width <= THROBBER_TEXT_X) return line;
|
|
158
|
+
const glyphFg = interpolateColor(t.brand, t.brandEnd, (Math.sin(state.frame / 6) + 1) / 2);
|
|
159
|
+
put(line, THROBBER_SPINNER_X, endX, state.spinner, glyphFg, true);
|
|
160
|
+
|
|
161
|
+
const { label, subject, tail } = textOf(state.activity);
|
|
162
|
+
const room = endX - THROBBER_TEXT_X;
|
|
163
|
+
const owner = state.owner ? `${state.owner}${SEP}` : '';
|
|
164
|
+
const head = owner + label;
|
|
165
|
+
const tailText = tail.length > 0 ? SEP + tail.join(SEP) : '';
|
|
166
|
+
const subjectText = subject ? SEP + subject : '';
|
|
167
|
+
let x = THROBBER_TEXT_X;
|
|
168
|
+
const fits = (w: number): boolean => w <= room;
|
|
169
|
+
const headW = getDisplayWidth(head);
|
|
170
|
+
const subjectW = getDisplayWidth(subjectText);
|
|
171
|
+
const tailW = getDisplayWidth(tailText);
|
|
172
|
+
// The label stays whole as long as it can; its trailing dots go before a letter does.
|
|
173
|
+
const bare = head.replace(/(\.\.\.|…)$/, '');
|
|
174
|
+
const headShown = fits(headW) ? head : fits(getDisplayWidth(bare)) ? bare : truncateDisplay(head, room);
|
|
175
|
+
if (owner && headShown.startsWith(owner)) {
|
|
176
|
+
x = put(line, x, endX, owner, t.textFaint);
|
|
177
|
+
x = put(line, x, endX, headShown.slice(owner.length), t.textMuted);
|
|
178
|
+
} else {
|
|
179
|
+
x = put(line, x, endX, headShown, t.textMuted);
|
|
180
|
+
}
|
|
181
|
+
const showTail = fits(headW + subjectW + tailW) || (subject === '' && fits(headW + tailW));
|
|
182
|
+
const subjectRoom = endX - x - (showTail ? tailW : 0);
|
|
183
|
+
if (subjectText && subjectRoom > getDisplayWidth(SEP) + 1) {
|
|
184
|
+
x = put(line, x, endX, truncateDisplay(subjectText, subjectRoom), t.text);
|
|
185
|
+
}
|
|
186
|
+
if (showTail && x + tailW <= endX) put(line, x, endX, tailText, t.textFaint);
|
|
187
|
+
return line;
|
|
188
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toast-center.ts, the toast primitive's state.
|
|
3
|
+
*
|
|
4
|
+
* A toast appears in the top right corner (drawn by renderToasts in
|
|
5
|
+
* surface-kit-parts.ts: a colored ┃ on both sides, the title bold, the body
|
|
6
|
+
* wrapped in full) and disappears after five seconds. Newest first; at most
|
|
7
|
+
* three are kept, older ones leave early rather than stacking down the screen.
|
|
8
|
+
*
|
|
9
|
+
* Producers: commandContext.showToast (any command), and the shared
|
|
10
|
+
* notification feed (bridgeNotificationFeedToToasts): every conversation
|
|
11
|
+
* system notice toasts ([WRFC] …, [Agents] …, compaction receipts; see
|
|
12
|
+
* core/notices.ts), and of the routed notifications only warning and critical
|
|
13
|
+
* ones do. Everything that toasts stays in the feed, which /notifications
|
|
14
|
+
* shows in full.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { NotificationFeedEntry, NotificationFeed } from '../views/notifications-feed.ts';
|
|
18
|
+
import type { ToastSpec, ToastTone } from './surface-kit-parts.ts';
|
|
19
|
+
|
|
20
|
+
/** How long a toast stays up. */
|
|
21
|
+
const TOAST_DURATION_MS = 5_000;
|
|
22
|
+
/** How many toasts are shown at once. */
|
|
23
|
+
const MAX_TOASTS = 3;
|
|
24
|
+
|
|
25
|
+
interface LiveToast {
|
|
26
|
+
readonly id: number;
|
|
27
|
+
readonly spec: ToastSpec;
|
|
28
|
+
readonly expiresAt: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ToastScheduler = (run: () => void, delayMs: number) => void;
|
|
32
|
+
|
|
33
|
+
const defaultScheduler: ToastScheduler = (run, delayMs) => {
|
|
34
|
+
const timer = setTimeout(run, delayMs);
|
|
35
|
+
// A pending toast expiry must never keep the process alive on exit.
|
|
36
|
+
(timer as { unref?: () => void }).unref?.();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export class ToastCenter {
|
|
40
|
+
private toasts: LiveToast[] = [];
|
|
41
|
+
private nextId = 1;
|
|
42
|
+
|
|
43
|
+
/** Called whenever the visible set changes (wired to the render request). */
|
|
44
|
+
onChange: (() => void) | null = null;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly clock: () => number = Date.now,
|
|
48
|
+
private readonly schedule: ToastScheduler = defaultScheduler,
|
|
49
|
+
) {}
|
|
50
|
+
|
|
51
|
+
show(spec: ToastSpec, durationMs: number = TOAST_DURATION_MS): void {
|
|
52
|
+
const toast: LiveToast = { id: this.nextId++, spec, expiresAt: this.clock() + durationMs };
|
|
53
|
+
this.toasts = [toast, ...this.toasts].slice(0, MAX_TOASTS);
|
|
54
|
+
this.schedule(() => this.expire(toast.id), durationMs);
|
|
55
|
+
this.onChange?.();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The toasts to draw now, newest first. */
|
|
59
|
+
visible(): ToastSpec[] {
|
|
60
|
+
const now = this.clock();
|
|
61
|
+
return this.toasts.filter((t) => t.expiresAt > now).map((t) => t.spec);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
clear(): void {
|
|
65
|
+
if (this.toasts.length === 0) return;
|
|
66
|
+
this.toasts = [];
|
|
67
|
+
this.onChange?.();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private expire(id: number): void {
|
|
71
|
+
const before = this.toasts.length;
|
|
72
|
+
this.toasts = this.toasts.filter((t) => t.id !== id);
|
|
73
|
+
if (this.toasts.length !== before) this.onChange?.();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let shared: ToastCenter | null = null;
|
|
78
|
+
|
|
79
|
+
/** The process-wide toast center (lazily created). */
|
|
80
|
+
export function getSharedToastCenter(): ToastCenter {
|
|
81
|
+
shared ??= new ToastCenter();
|
|
82
|
+
return shared;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function toneForLevel(level: NotificationFeedEntry['level']): ToastTone | null {
|
|
86
|
+
if (level === 'critical') return 'error';
|
|
87
|
+
if (level === 'warning') return 'warning';
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A system notice always toasts; its level picks the bar color. */
|
|
92
|
+
function toneForNotice(level: NotificationFeedEntry['level']): ToastTone {
|
|
93
|
+
return toneForLevel(level) ?? 'info';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Toast every new warning or critical entry of the notification feed. A
|
|
98
|
+
* collapsed burst toasts once per growth of its running count, with the count
|
|
99
|
+
* in the title. Returns an unsubscribe function.
|
|
100
|
+
*/
|
|
101
|
+
export function bridgeNotificationFeedToToasts(feed: NotificationFeed, toasts: ToastCenter): () => void {
|
|
102
|
+
// Per entry: the count last toasted for, and whether it has toasted at that
|
|
103
|
+
// count. An entry toasts when its count grows, or once when a second arrival
|
|
104
|
+
// of the same event (feed eventKey folding) makes a quiet entry one that
|
|
105
|
+
// toasts; a fold never toasts an entry twice.
|
|
106
|
+
const seen = new Map<string, { readonly count: number; readonly toasted: boolean }>();
|
|
107
|
+
for (const entry of feed.list()) seen.set(entry.key, { count: entry.collapsedCount, toasted: true });
|
|
108
|
+
return feed.subscribe(() => {
|
|
109
|
+
for (const entry of feed.list()) {
|
|
110
|
+
const previous = seen.get(entry.key);
|
|
111
|
+
const tone = entry.toast === 'never' ? null : entry.toast === 'always' ? toneForNotice(entry.level) : toneForLevel(entry.level);
|
|
112
|
+
if (previous && previous.count === entry.collapsedCount && (previous.toasted || !tone)) continue;
|
|
113
|
+
seen.set(entry.key, { count: entry.collapsedCount, toasted: Boolean(tone) });
|
|
114
|
+
if (!tone) continue;
|
|
115
|
+
const title = entry.collapsedCount > 1 ? `${entry.title} (${entry.collapsedCount} times)` : entry.title;
|
|
116
|
+
toasts.show({ title, body: entry.body, tone });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|