@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,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transcript-scroll.ts, where the main transcript is scrolled to.
|
|
3
|
+
*
|
|
4
|
+
* top the first transcript line on screen
|
|
5
|
+
* locked parked at the live bottom: each frame follows the tail
|
|
6
|
+
*
|
|
7
|
+
* Scrolling up (the mouse wheel, PageUp, Up past the input history) unlocks
|
|
8
|
+
* it; reaching the bottom again, Esc (handler-modal-stack.ts), or submitting
|
|
9
|
+
* input locks it again. While it is unlocked the back-to-bottom pill shows
|
|
10
|
+
* above the input area (back-to-bottom.ts) and Esc returns to the bottom
|
|
11
|
+
* instead of interrupting the running turn.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export class TranscriptScroll {
|
|
15
|
+
/** The first transcript line on screen. */
|
|
16
|
+
top = 0;
|
|
17
|
+
/** Parked at the live bottom, following the tail. */
|
|
18
|
+
locked = true;
|
|
19
|
+
/** The clamp the last frame computed (overlay- and footer-aware); null before the first frame. */
|
|
20
|
+
private lastMax: number | null = null;
|
|
21
|
+
|
|
22
|
+
/** Away from the live bottom: the pill shows and Esc goes back down. */
|
|
23
|
+
get scrolledBack(): boolean {
|
|
24
|
+
return !this.locked;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Scroll by `delta` lines (negative is up). `fallbackMax` is used only
|
|
29
|
+
* before the first frame has computed the real clamp.
|
|
30
|
+
*/
|
|
31
|
+
scrollBy(delta: number, fallbackMax: () => number): void {
|
|
32
|
+
const max = this.lastMax ?? fallbackMax();
|
|
33
|
+
this.top = Math.max(0, Math.min(this.top + delta, max));
|
|
34
|
+
this.locked = this.top >= max;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Back to the live bottom: the next frame follows the tail again. */
|
|
38
|
+
toBottom(): void {
|
|
39
|
+
this.locked = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Show `line` at the top (a bookmark, a search hit); unlocks unless that is the bottom. */
|
|
43
|
+
jumpTo(line: number, max?: number): void {
|
|
44
|
+
this.locked = false;
|
|
45
|
+
this.top = Math.max(0, max === undefined ? line : Math.min(line, max));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Follow the tail while locked (the stream path). */
|
|
49
|
+
followTail(lineCount: number, viewportHeight: number): void {
|
|
50
|
+
if (!this.locked) return;
|
|
51
|
+
this.top = Math.max(0, lineCount - viewportHeight);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Take the frame's clamp. Unlocked but already at the bottom (a jump that
|
|
56
|
+
* landed there, output that shrank) is the live bottom: lock again.
|
|
57
|
+
* Returns true when that re-lock happened.
|
|
58
|
+
*/
|
|
59
|
+
settle(nextTop: number, maxScroll: number): boolean {
|
|
60
|
+
this.top = nextTop;
|
|
61
|
+
this.lastMax = maxScroll;
|
|
62
|
+
if (!this.locked && nextTop >= maxScroll) {
|
|
63
|
+
this.locked = true;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/shell/ui-openers.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
+
import { openModelPickerNow, type ModelPickerOpenDeps } from '../input/model-picker-open.ts';
|
|
2
3
|
import { getProviderIdFromModel } from '@pellux/goodvibes-sdk/platform/providers';
|
|
3
|
-
import type { ConversationManager } from '../core/conversation';
|
|
4
4
|
import type { CommandContext } from '../input/command-registry.ts';
|
|
5
5
|
import type { InputHandler } from '../input/handler.ts';
|
|
6
|
-
import type {
|
|
6
|
+
import type { ShellViews } from '../views/builtin-views.ts';
|
|
7
|
+
import type { ViewPanelAdapter } from '../views/view-panel-adapter.ts';
|
|
8
|
+
import { wireViewOpeners } from './view-openers.ts';
|
|
7
9
|
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
8
10
|
import type { MutableRuntimeState } from '@/runtime/index.ts';
|
|
9
11
|
import type { FeatureFlagManager } from '@/runtime/index.ts';
|
|
@@ -13,11 +15,14 @@ import type { SecretsManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
13
15
|
import type { MemoryEmbeddingProviderRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
14
16
|
import type { ServiceInspectionQuery } from '@/runtime/index.ts';
|
|
15
17
|
import type { EmbeddingProviderPickerEntry, ModelPickerTargetInfo } from '../input/model-picker.ts';
|
|
16
|
-
import
|
|
18
|
+
import { CommandPalette, buildPaletteEntries } from '../input/command-palette.ts';
|
|
19
|
+
import { confirmThrough } from '../input/confirm-dialog.ts';
|
|
20
|
+
import { bridgeNotificationFeedToToasts, getSharedToastCenter } from '../renderer/toast-center.ts';
|
|
21
|
+
import { getSharedNotificationFeed } from '../views/notifications-feed.ts';
|
|
17
22
|
import { categorizeBuiltinCommands } from '../input/commands.ts';
|
|
18
23
|
import { syncServiceSettingToPlatform } from './service-settings-sync.ts';
|
|
19
|
-
import { setActiveThemeMode } from '../renderer/theme.ts';
|
|
20
|
-
import { THEME_MODE_CONFIG_KEY, coerceThemeModeSetting } from '../renderer/theme-mode-config.ts';
|
|
24
|
+
import { setActiveThemeMode, setActiveThemeName } from '../renderer/theme.ts';
|
|
25
|
+
import { THEME_MODE_CONFIG_KEY, THEME_NAME_CONFIG_KEY, coerceThemeModeSetting } from '../renderer/theme-mode-config.ts';
|
|
21
26
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
22
27
|
import { buildFirstOpenItems, decodeFirstOpenChoice, selfRecordWorkspaceRegistration } from '../cli/tui-startup.ts';
|
|
23
28
|
import type { SettingsDaemonCredentialWriter } from '../input/settings-modal-secrets.ts';
|
|
@@ -27,8 +32,10 @@ import type { WorkspaceTrustLevel } from '@pellux/goodvibes-sdk/platform/runtime
|
|
|
27
32
|
type WireShellUiOpenersOptions = {
|
|
28
33
|
commandContext: CommandContext;
|
|
29
34
|
input: InputHandler;
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
/** The read models and config-modal surfaces behind the built-in modals. */
|
|
36
|
+
views: ShellViews;
|
|
37
|
+
/** The operator API's panels.list / panels.open, answered by the modal views. */
|
|
38
|
+
viewPanelAdapter: ViewPanelAdapter;
|
|
32
39
|
configManager: ConfigManager;
|
|
33
40
|
providerRegistry: ProviderRegistry;
|
|
34
41
|
runtime: MutableRuntimeState;
|
|
@@ -125,8 +132,8 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
125
132
|
const {
|
|
126
133
|
commandContext,
|
|
127
134
|
input,
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
views,
|
|
136
|
+
viewPanelAdapter,
|
|
130
137
|
configManager,
|
|
131
138
|
providerRegistry,
|
|
132
139
|
runtime,
|
|
@@ -284,70 +291,39 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
284
291
|
];
|
|
285
292
|
};
|
|
286
293
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const embeddingEntries = await resolveEmbeddingProviderEntries();
|
|
313
|
-
input.modelPicker.embeddingProviders = embeddingEntries;
|
|
314
|
-
input.modalOpened('modelPicker');
|
|
315
|
-
input.modelPicker.setTargetInfos(buildModelPickerTargets(embeddingEntries));
|
|
316
|
-
input.modelPicker.openAllModels(models, getCurrentModelForPickerTarget());
|
|
317
|
-
render();
|
|
318
|
-
})().catch((error: unknown) => {
|
|
319
|
-
commandContext.print?.(`Model picker failed to open: ${summarizeError(error)}`);
|
|
320
|
-
render();
|
|
321
|
-
});
|
|
294
|
+
// The picker opens at once on the cached catalog; the credential-source
|
|
295
|
+
// reads, the embeddings probe and the live model re-check fill in after
|
|
296
|
+
// (input/model-picker-open.ts). getSelectableModels() and listModels() can
|
|
297
|
+
// name catalog providers this runtime never registered; both lists keep
|
|
298
|
+
// runtime-registered providers only, so the picker never offers a model
|
|
299
|
+
// that fails with "Provider not registered" at turn time.
|
|
300
|
+
const pickerOpenDeps: ModelPickerOpenDeps = {
|
|
301
|
+
picker: input.modelPicker,
|
|
302
|
+
modalOpened: () => input.modalOpened('modelPicker'),
|
|
303
|
+
render,
|
|
304
|
+
listModels: () => providerRegistry.getSelectableModels().filter((m) => providerRegistry.has(m.provider)),
|
|
305
|
+
listProviders: () => [...new Set(providerRegistry.listModels().map((m) => m.provider))].filter((id) => providerRegistry.has(id)),
|
|
306
|
+
currentModelId: getCurrentModelForPickerTarget,
|
|
307
|
+
currentProviderId: getCurrentProviderForPickerTarget,
|
|
308
|
+
configuredProviderIds: () => new Set(getConfiguredProviderIds()),
|
|
309
|
+
buildConfiguredVia: (ids, configured, secretIds) => buildConfiguredViaMap([...ids], new Set(configured), subscriptionManager, secretIds),
|
|
310
|
+
buildTargets: buildModelPickerTargets,
|
|
311
|
+
resolveSecretProviderIds,
|
|
312
|
+
resolveEmbeddingProviders: resolveEmbeddingProviderEntries,
|
|
313
|
+
refreshLiveModels: async () => ((await providerRegistry.refreshLiveModelDiscovery?.()) ?? []).some((r) => r.added.length > 0 || r.removed.length > 0),
|
|
314
|
+
prefetch: async () => {
|
|
315
|
+
input.modelPicker.pinnedIds = new Set(await getPinned());
|
|
316
|
+
await input.modelPicker.loadRecentModels();
|
|
317
|
+
},
|
|
318
|
+
onError: (error) => { commandContext.print?.(`Model picker could not load everything: ${summarizeError(error)}`); render(); },
|
|
322
319
|
};
|
|
323
320
|
|
|
321
|
+
commandContext.openModelPicker = () => { void openModelPickerNow(pickerOpenDeps, 'models'); };
|
|
322
|
+
|
|
324
323
|
commandContext.openModelPickerWithTarget = (target) => input.openModelPickerWithTarget(target);
|
|
325
324
|
commandContext.openProviderModelPickerWithTarget = (target) => input.openProviderModelPickerWithTarget(target);
|
|
326
325
|
|
|
327
|
-
commandContext.openProviderPicker = () => {
|
|
328
|
-
void (async () => {
|
|
329
|
-
// listModels() surfaces every catalog provider id, not just the ones
|
|
330
|
-
// actually registered on this runtime (see openModelPicker above for the
|
|
331
|
-
// same class of bug). Intersect against providerRegistry.has() so the
|
|
332
|
-
// provider picker never offers a provider that will fail with
|
|
333
|
-
// "Provider not registered" once a model under it is selected.
|
|
334
|
-
const providers = [...new Set(providerRegistry.listModels().map((model) => model.provider))]
|
|
335
|
-
.filter((providerId) => providerRegistry.has(providerId));
|
|
336
|
-
const configuredIds = new Set(getConfiguredProviderIds());
|
|
337
|
-
input.modelPicker.configuredProviders = configuredIds;
|
|
338
|
-
const secretProviderIds = await resolveSecretProviderIds();
|
|
339
|
-
input.modelPicker.configuredViaMap = buildConfiguredViaMap(providers, configuredIds, subscriptionManager, secretProviderIds);
|
|
340
|
-
const embeddingEntries = await resolveEmbeddingProviderEntries();
|
|
341
|
-
input.modelPicker.embeddingProviders = embeddingEntries;
|
|
342
|
-
input.modalOpened('modelPicker');
|
|
343
|
-
input.modelPicker.setTargetInfos(buildModelPickerTargets(embeddingEntries));
|
|
344
|
-
input.modelPicker.openProviders(providers, getCurrentProviderForPickerTarget());
|
|
345
|
-
render();
|
|
346
|
-
})().catch((error: unknown) => {
|
|
347
|
-
commandContext.print?.(`Provider picker failed to open: ${summarizeError(error)}`);
|
|
348
|
-
render();
|
|
349
|
-
});
|
|
350
|
-
};
|
|
326
|
+
commandContext.openProviderPicker = () => { void openModelPickerNow(pickerOpenDeps, 'providers'); };
|
|
351
327
|
|
|
352
328
|
commandContext.completeEmbeddingProviderSelection = (providerId: string) => {
|
|
353
329
|
try {
|
|
@@ -382,6 +358,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
382
358
|
if (!input.helpOverlayActive) input.modalOpened('help');
|
|
383
359
|
input.helpOverlayActive = !input.helpOverlayActive;
|
|
384
360
|
input.helpScrollOffset = 0;
|
|
361
|
+
input.overlayFilters.help.clear();
|
|
385
362
|
render();
|
|
386
363
|
};
|
|
387
364
|
|
|
@@ -398,6 +375,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
398
375
|
if (!input.shortcutsOverlayActive) input.modalOpened('shortcuts');
|
|
399
376
|
input.shortcutsOverlayActive = !input.shortcutsOverlayActive;
|
|
400
377
|
input.shortcutsScrollOffset = 0;
|
|
378
|
+
input.overlayFilters.shortcuts.clear();
|
|
401
379
|
render();
|
|
402
380
|
};
|
|
403
381
|
|
|
@@ -427,6 +405,12 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
427
405
|
onSettingApplied: (change) => {
|
|
428
406
|
// forced dark/light applies immediately (rebuild palettes + full
|
|
429
407
|
// repaint); auto only re-probes at startup, so it takes effect next launch.
|
|
408
|
+
if (String(change.key) === THEME_NAME_CONFIG_KEY) {
|
|
409
|
+
// Theme changes apply immediately: rebuild palettes + full repaint.
|
|
410
|
+
const applied = setActiveThemeName(change.value);
|
|
411
|
+
commandContext.requestFullRepaint?.();
|
|
412
|
+
return { message: `Theme: ${applied} (applied now)` };
|
|
413
|
+
}
|
|
430
414
|
if (String(change.key) === THEME_MODE_CONFIG_KEY) {
|
|
431
415
|
const next = coerceThemeModeSetting(change.value);
|
|
432
416
|
if (next === 'dark' || next === 'light') {
|
|
@@ -446,15 +430,14 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
446
430
|
render();
|
|
447
431
|
};
|
|
448
432
|
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
// registered surface degrades honestly to a print rather than a blank modal.
|
|
433
|
+
// Open a config-modal surface by name (or by an old name that redirects to
|
|
434
|
+
// one, e.g. 'accounts' opens 'providers-modal'). Surfaces are registered in
|
|
435
|
+
// builtin-modals.ts; a name with no registered surface degrades honestly to
|
|
436
|
+
// a print rather than a blank modal.
|
|
454
437
|
// The stack name is the stable 'config' slot (one config modal at a time,
|
|
455
438
|
// opening another swaps the surface), so Esc close/return and the modal-stack
|
|
456
439
|
// machinery need only the single 'config' case (handler-ui-state.ts).
|
|
457
|
-
// Some
|
|
440
|
+
// Some legacy view-name redirects (and migrated front-doors) resolve to a NATIVE
|
|
458
441
|
// modal that is not a ConfigModalSurface, e.g. `sessions` -> `sessionPicker`,
|
|
459
442
|
// where 'sessionPicker' is the real session-picker modal opened by
|
|
460
443
|
// commandContext.openSessionPicker below, NOT a registered config surface.
|
|
@@ -475,7 +458,8 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
475
458
|
openNative();
|
|
476
459
|
return;
|
|
477
460
|
}
|
|
478
|
-
const
|
|
461
|
+
const registry = views.modalSurfaces;
|
|
462
|
+
const surface = registry.getModalSurface(name) ?? registry.getModalSurface(registry.getModalRedirect(name) ?? '');
|
|
479
463
|
if (!surface) {
|
|
480
464
|
commandContext.print(`'${name}' is not available yet in this build.`);
|
|
481
465
|
render();
|
|
@@ -485,7 +469,6 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
485
469
|
input.configModal.open(surface, render);
|
|
486
470
|
render();
|
|
487
471
|
};
|
|
488
|
-
panelManager.setOpenModalCallback(commandContext.openModal);
|
|
489
472
|
|
|
490
473
|
commandContext.openMcpWorkspace = () => {
|
|
491
474
|
input.openMcpWorkspace(commandContext);
|
|
@@ -498,124 +481,53 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
498
481
|
render();
|
|
499
482
|
};
|
|
500
483
|
|
|
501
|
-
commandContext.openPanelPicker = () => {
|
|
502
|
-
// Focus ownership lives in PanelManager (focusTarget); read it there rather
|
|
503
|
-
// than tracking a parallel input.panelFocused flag. Toggle semantics: if the
|
|
504
|
-
// workspace is visible AND already focused, hide it; otherwise reveal and
|
|
505
|
-
// focus it (opening a picker when nothing is open yet).
|
|
506
|
-
if (panelManager.isVisible() && panelManager.getFocusTarget() === 'panel') {
|
|
507
|
-
panelManager.hide();
|
|
508
|
-
panelManager.focusPrompt();
|
|
509
|
-
conversation.setSplashSuppressed(false);
|
|
510
|
-
conversation.rebuildHistory();
|
|
511
|
-
render();
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
if (panelManager.getAllOpen().length === 0) {
|
|
515
|
-
// (the purge): 'panel-list' (a browse-all-panels picker PANEL)
|
|
516
|
-
// was DELETE-disposition, a picker over a handful of panels is dead
|
|
517
|
-
// weight now. Its replacement is this selection MODAL, built from the
|
|
518
|
-
// live registry (PanelManager.getRegisteredTypes()) rather than a
|
|
519
|
-
// hardcoded list, so it can never list a retired/deleted id.
|
|
520
|
-
const items: SelectionItem[] = panelManager.getRegisteredTypes().map((entry) => ({
|
|
521
|
-
id: entry.id,
|
|
522
|
-
label: `${entry.icon} ${entry.name}`,
|
|
523
|
-
detail: entry.description,
|
|
524
|
-
category: entry.category,
|
|
525
|
-
primaryAction: 'select',
|
|
526
|
-
}));
|
|
527
|
-
commandContext.openSelection?.('Open Panel', items, { allowSearch: true }, (result) => {
|
|
528
|
-
if (!result) return;
|
|
529
|
-
panelManager.open(result.item.id);
|
|
530
|
-
panelManager.show();
|
|
531
|
-
panelManager.focusPanels();
|
|
532
|
-
conversation.setSplashSuppressed(true);
|
|
533
|
-
conversation.rebuildHistory();
|
|
534
|
-
render();
|
|
535
|
-
});
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
panelManager.show();
|
|
539
|
-
panelManager.focusPanels();
|
|
540
|
-
conversation.setSplashSuppressed(true);
|
|
541
|
-
conversation.rebuildHistory();
|
|
542
|
-
render();
|
|
543
|
-
};
|
|
544
|
-
|
|
545
484
|
// Registry-driven surfaces built inside command handlers (/help) group by
|
|
546
485
|
// the same categories as the palette and the generated reference.
|
|
547
486
|
commandContext.getCommandCategories = () => getCommandCategoryMap();
|
|
548
487
|
|
|
488
|
+
input.surfaceModals.onChange = render;
|
|
489
|
+
commandContext.confirm = (options) => confirmThrough(input.surfaceModals, options);
|
|
490
|
+
const toasts = getSharedToastCenter();
|
|
491
|
+
toasts.onChange = render;
|
|
492
|
+
commandContext.showToast = (toast) => toasts.show(toast);
|
|
493
|
+
bridgeNotificationFeedToToasts(getSharedNotificationFeed(), toasts);
|
|
494
|
+
|
|
549
495
|
commandContext.openCommandPalette = () => {
|
|
550
496
|
// The palette is built live from the command registry, never a hardcoded
|
|
551
497
|
// list, so it can never drift from the real command set. Category labels
|
|
552
498
|
// come from the same source the generated docs use (getCommandCategoryMap).
|
|
553
499
|
const registry = input.commandRegistry;
|
|
554
500
|
if (!registry) return;
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
.
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
category: categoryByName.get(cmd.name) ?? 'Other',
|
|
571
|
-
primaryAction: 'select' as const,
|
|
572
|
-
};
|
|
573
|
-
});
|
|
574
|
-
commandContext.openSelection?.(
|
|
575
|
-
'Command Palette',
|
|
576
|
-
items,
|
|
577
|
-
{ allowSearch: true, primaryVerbLabel: 'Run' },
|
|
578
|
-
(result) => {
|
|
579
|
-
if (!result) return;
|
|
580
|
-
const cmd = registry.get(result.item.id);
|
|
581
|
-
if (!cmd) return;
|
|
582
|
-
// Pre-fill the composer with the command (and a trailing space so the
|
|
583
|
-
// inline args hint shows what the command wants). Keyboard-first: focus
|
|
584
|
-
// returns to the composer with command mode armed so the user fills any
|
|
585
|
-
// args and presses Enter, matching tab-completion's fill behavior.
|
|
586
|
-
input.prompt = `/${cmd.name} `;
|
|
501
|
+
const top = input.surfaceModals.top();
|
|
502
|
+
if (top instanceof CommandPalette) return;
|
|
503
|
+
const palette = new CommandPalette({
|
|
504
|
+
entries: buildPaletteEntries(registry.getAll(), getCommandCategoryMap()),
|
|
505
|
+
describe: (id) => (id === 'model' ? commandContext.session.runtime.model || undefined : undefined),
|
|
506
|
+
onRun: (entry, mode) => {
|
|
507
|
+
if (mode === 'run' && commandContext.executeCommand) {
|
|
508
|
+
void commandContext.executeCommand(entry.id, []).finally(() => render());
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
// Fill the composer with the command (and a trailing space so the
|
|
512
|
+
// inline args hint shows what the command wants), command mode armed,
|
|
513
|
+
// so the user types any arguments and presses Enter, matching
|
|
514
|
+
// tab-completion's fill behavior.
|
|
515
|
+
input.prompt = `/${entry.id} `;
|
|
587
516
|
input.cursorPos = input.prompt.length;
|
|
588
517
|
input.commandMode = true;
|
|
589
518
|
input.autocomplete?.reset();
|
|
590
519
|
commandContext.focusPrompt?.();
|
|
591
520
|
render();
|
|
592
521
|
},
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
commandContext.focusPanels = () => {
|
|
597
|
-
panelManager.focusPanels();
|
|
522
|
+
});
|
|
523
|
+
input.surfaceModals.push(palette);
|
|
598
524
|
render();
|
|
599
525
|
};
|
|
600
526
|
|
|
601
527
|
commandContext.focusPrompt = () => {
|
|
602
|
-
panelManager.focusPrompt();
|
|
603
528
|
input.indicatorFocused = false;
|
|
604
529
|
render();
|
|
605
530
|
};
|
|
606
531
|
|
|
607
|
-
commandContext
|
|
608
|
-
// forward the deep-link target so a jumped-to panel lands on the
|
|
609
|
-
// right row (fleet --target); undefined for ordinary opens.
|
|
610
|
-
panelManager.open(panelId, pane, target);
|
|
611
|
-
panelManager.show();
|
|
612
|
-
// focus rule 1a: every registered caller of showPanel is a slash
|
|
613
|
-
// command (/panel open, /routes, /approval, /tasks, /ops-control, ...),
|
|
614
|
-
// the command path leaves focus in the composer ("the user is
|
|
615
|
-
// mid-command-flow") unless the caller explicitly asks to grab it.
|
|
616
|
-
if (opts?.focus) panelManager.focusPanels();
|
|
617
|
-
conversation.setSplashSuppressed(true);
|
|
618
|
-
conversation.rebuildHistory();
|
|
619
|
-
render();
|
|
620
|
-
};
|
|
532
|
+
wireViewOpeners({ commandContext, input, views, viewPanelAdapter, render });
|
|
621
533
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* view-openers.ts, the openers for the built-in modals: Agents, Usage,
|
|
3
|
+
* Changes (and its previews), Notifications and the local-auth password
|
|
4
|
+
* prompt, plus ctx.openView, which turns any current or old view name
|
|
5
|
+
* ('fleet', 'cockpit', 'tokens', 'git', 'providers', …) into the modal that
|
|
6
|
+
* holds it now (input/views.ts).
|
|
7
|
+
*
|
|
8
|
+
* Every modal is a kit SurfaceModal pushed on the input handler's modal host.
|
|
9
|
+
* Opening one that is already open brings it to the top instead of stacking a
|
|
10
|
+
* second copy.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
14
|
+
import type { InputHandler } from '../input/handler.ts';
|
|
15
|
+
import type { SurfaceModal } from '../input/surface-modal-host.ts';
|
|
16
|
+
import type { ShellViews } from '../views/builtin-views.ts';
|
|
17
|
+
import type { ViewPanelAdapter } from '../views/view-panel-adapter.ts';
|
|
18
|
+
import { AgentsModal } from '../input/agents-modal.ts';
|
|
19
|
+
import { UsageModal } from '../input/usage-modal.ts';
|
|
20
|
+
import { ChangesModal, type ChangesDeps } from '../input/changes-modal.ts';
|
|
21
|
+
import { NotificationsModal } from '../input/notifications-modal.ts';
|
|
22
|
+
import { MaskedEntryModal } from '../input/masked-entry-modal.ts';
|
|
23
|
+
import { confirmThrough } from '../input/confirm-dialog.ts';
|
|
24
|
+
import { resolveViewName, type ViewTarget } from '../input/views.ts';
|
|
25
|
+
import { getSharedNotificationFeed } from '../views/notifications-feed.ts';
|
|
26
|
+
import { getSharedHostedSessionFeed } from '../views/hosted-session-feed.ts';
|
|
27
|
+
import { revertReviewHunk } from '../input/commands/review-runtime.ts';
|
|
28
|
+
|
|
29
|
+
export interface WireViewOpenersOptions {
|
|
30
|
+
readonly commandContext: CommandContext;
|
|
31
|
+
readonly input: InputHandler;
|
|
32
|
+
readonly views: ShellViews;
|
|
33
|
+
readonly viewPanelAdapter: ViewPanelAdapter;
|
|
34
|
+
readonly render: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Split a preview text into a diff and a note: `@@ label @@\n text` is a note, not a hunk. */
|
|
38
|
+
function previewParts(text: string): { diff: string | null; note: string | null } {
|
|
39
|
+
const first = text.split('\n', 1)[0] ?? '';
|
|
40
|
+
if (/^@@ (?!-)/.test(first)) return { diff: null, note: text.slice(first.length + 1).trim() || null };
|
|
41
|
+
return { diff: text, note: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function wireViewOpeners(options: WireViewOpenersOptions): void {
|
|
45
|
+
const { commandContext, input, views, viewPanelAdapter, render } = options;
|
|
46
|
+
const host = input.surfaceModals;
|
|
47
|
+
|
|
48
|
+
const findOpen = <T extends SurfaceModal>(type: abstract new (...args: never[]) => T): T | undefined =>
|
|
49
|
+
host.modals().find((modal): modal is T => modal instanceof type);
|
|
50
|
+
|
|
51
|
+
const show = (modal: SurfaceModal): void => {
|
|
52
|
+
host.push(modal);
|
|
53
|
+
render();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// ── Agents ────────────────────────────────────────────────────────────────
|
|
57
|
+
commandContext.openAgents = (opts = {}) => {
|
|
58
|
+
const modal = findOpen(AgentsModal) ?? new AgentsModal({
|
|
59
|
+
readModel: views.fleet.readModel,
|
|
60
|
+
actions: views.fleet.actions,
|
|
61
|
+
acts: views.fleet.acts,
|
|
62
|
+
spawn: views.fleet.spawn,
|
|
63
|
+
configManager: views.configManager,
|
|
64
|
+
hosted: getSharedHostedSessionFeed(),
|
|
65
|
+
steerHosted: (text) => { void commandContext.executeCommand?.('hosted', ['say', ...text.split(/\s+/)]); },
|
|
66
|
+
sessionCost: () => {
|
|
67
|
+
const cost = views.usage.sessionCost();
|
|
68
|
+
return cost.priced ? cost.usd : null;
|
|
69
|
+
},
|
|
70
|
+
confirm: (confirmOptions) => confirmThrough(host, confirmOptions),
|
|
71
|
+
requestRender: render,
|
|
72
|
+
openSessionView: (target) => commandContext.openSessionView?.(target) ?? false,
|
|
73
|
+
});
|
|
74
|
+
if (opts.target) modal.reveal(opts.target);
|
|
75
|
+
if (opts.hosted) modal.showHosted();
|
|
76
|
+
show(modal);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// ── Usage ─────────────────────────────────────────────────────────────────
|
|
80
|
+
commandContext.openUsage = (opts = {}) => {
|
|
81
|
+
const existing = findOpen(UsageModal);
|
|
82
|
+
if (existing) {
|
|
83
|
+
if (opts.tab) existing.tab = opts.tab;
|
|
84
|
+
show(existing);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
show(new UsageModal({
|
|
88
|
+
tracker: views.usage,
|
|
89
|
+
tab: opts.tab,
|
|
90
|
+
compact: () => { void commandContext.executeCommand?.('compact', []).finally(render); },
|
|
91
|
+
requestRender: render,
|
|
92
|
+
}));
|
|
93
|
+
};
|
|
94
|
+
// A click on the footer's token or context row opens Usage.
|
|
95
|
+
input.onFooterTarget = (target) => {
|
|
96
|
+
if (target === 'usage') commandContext.openUsage?.();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ── Changes ───────────────────────────────────────────────────────────────
|
|
100
|
+
const changesDeps: ChangesDeps = {
|
|
101
|
+
workingDirectory: views.workingDirectory,
|
|
102
|
+
getSessionFiles: views.getSessionFiles,
|
|
103
|
+
requestRender: render,
|
|
104
|
+
submitInput: (text) => commandContext.submitInput?.(text),
|
|
105
|
+
openInEditor: (path, line) => commandContext.openFileInEditor?.(path, line),
|
|
106
|
+
revertHunk: (hunk) => revertReviewHunk(commandContext, hunk),
|
|
107
|
+
confirm: (confirmOptions) => confirmThrough(host, confirmOptions),
|
|
108
|
+
};
|
|
109
|
+
// One workspace for the session: reviewed marks and attached comments survive closing it.
|
|
110
|
+
const workspace = new ChangesModal(changesDeps, 'workspace');
|
|
111
|
+
commandContext.openChanges = (opts = {}) => {
|
|
112
|
+
show(workspace);
|
|
113
|
+
void workspace.reload(opts.source ?? workspace.source);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const preview = (title: string, text: string | null, note: string | null): ChangesModal => {
|
|
117
|
+
const modal = new ChangesModal(changesDeps, 'preview');
|
|
118
|
+
const parts = text ? previewParts(text) : { diff: null, note: null };
|
|
119
|
+
modal.loadPreview(title, parts.diff, note ?? parts.note);
|
|
120
|
+
show(modal);
|
|
121
|
+
return modal;
|
|
122
|
+
};
|
|
123
|
+
commandContext.previewChanges = async (opts) => {
|
|
124
|
+
const modal = preview(opts.title, opts.diff ?? null, opts.note ?? null);
|
|
125
|
+
if (!opts.question) return false;
|
|
126
|
+
return modal.ask(opts.question);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// The fleet acts show candidate diffs and ask over them through this.
|
|
130
|
+
let fleetPreview: ChangesModal | null = null;
|
|
131
|
+
views.bridge.diffSurface = {
|
|
132
|
+
show: (title, diff) => {
|
|
133
|
+
if (fleetPreview) host.close(fleetPreview, 'done');
|
|
134
|
+
fleetPreview = preview(title, diff, null);
|
|
135
|
+
},
|
|
136
|
+
armConfirm: (arm) => {
|
|
137
|
+
const tone = arm.verb === 'Discard' ? 'danger' as const : 'warning' as const;
|
|
138
|
+
const asked = fleetPreview && host.modals().includes(fleetPreview)
|
|
139
|
+
? fleetPreview.ask({ text: arm.label, confirmLabel: arm.verb, tone })
|
|
140
|
+
: confirmThrough(host, { title: `${arm.verb}?`, body: arm.label, confirmLabel: arm.verb, tone });
|
|
141
|
+
void asked.then((ok) => {
|
|
142
|
+
if (ok) void arm.onConfirm();
|
|
143
|
+
else arm.onCancel?.();
|
|
144
|
+
render();
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
close: () => {
|
|
148
|
+
if (fleetPreview) host.close(fleetPreview, 'done');
|
|
149
|
+
fleetPreview = null;
|
|
150
|
+
render();
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// ── Notifications ─────────────────────────────────────────────────────────
|
|
155
|
+
const redirect = (name: string): string | undefined => views.modalSurfaces.getModalRedirect(name);
|
|
156
|
+
commandContext.openNotifications = () => {
|
|
157
|
+
show(findOpen(NotificationsModal) ?? new NotificationsModal({
|
|
158
|
+
feed: getSharedNotificationFeed(),
|
|
159
|
+
resolveSubject: (subject) => (resolveViewName(subject, redirect) ? () => { openView(subject); } : null),
|
|
160
|
+
}));
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ── Local-auth password prompt ────────────────────────────────────────────
|
|
164
|
+
const openMaskedEntry = (kind: 'add-user' | 'rotate-password', username?: string): void => {
|
|
165
|
+
show(new MaskedEntryModal({
|
|
166
|
+
kind,
|
|
167
|
+
username,
|
|
168
|
+
auth: views.localUserAuthManager,
|
|
169
|
+
onDone: (message) => { commandContext.print(message); render(); },
|
|
170
|
+
}));
|
|
171
|
+
};
|
|
172
|
+
views.bridge.openMaskedEntry = openMaskedEntry;
|
|
173
|
+
commandContext.openLocalAuthMaskedEntry = openMaskedEntry;
|
|
174
|
+
|
|
175
|
+
// ── Any view by name ──────────────────────────────────────────────────────
|
|
176
|
+
const openView = (name: string, target?: ViewTarget): boolean => {
|
|
177
|
+
const route = resolveViewName(name, redirect);
|
|
178
|
+
if (!route) return false;
|
|
179
|
+
switch (route.kind) {
|
|
180
|
+
case 'agents': commandContext.openAgents?.({ target, hosted: route.hosted }); break;
|
|
181
|
+
case 'usage': commandContext.openUsage?.({ tab: route.tab }); break;
|
|
182
|
+
case 'changes': commandContext.openChanges?.({ source: route.mode === 'git' ? 'working' : 'session' }); break;
|
|
183
|
+
case 'notifications': commandContext.openNotifications?.(); break;
|
|
184
|
+
case 'sessions': commandContext.openSessionPicker?.(); break;
|
|
185
|
+
case 'modal': commandContext.openModal?.(route.name); break;
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
};
|
|
189
|
+
commandContext.openView = openView;
|
|
190
|
+
viewPanelAdapter.setOpener((id) => openView(id));
|
|
191
|
+
|
|
192
|
+
// The older per-console callbacks commands still call, each onto its modal.
|
|
193
|
+
commandContext.openForensicsView = () => { openView('forensics'); };
|
|
194
|
+
commandContext.openIncidentView = () => { openView('incident'); };
|
|
195
|
+
commandContext.openPolicyView = () => { openView('policy'); };
|
|
196
|
+
commandContext.openHooksView = () => { openView('hooks'); };
|
|
197
|
+
commandContext.openCommunicationView = () => { openView('communication'); };
|
|
198
|
+
commandContext.openOrchestrationView = () => { openView('orchestration'); };
|
|
199
|
+
commandContext.openCockpitView = () => { openView('cockpit'); };
|
|
200
|
+
commandContext.openSecurityView = () => { openView('security'); };
|
|
201
|
+
commandContext.openKnowledgeView = () => { openView('knowledge'); };
|
|
202
|
+
commandContext.openMemoryView = () => { openView('memory'); };
|
|
203
|
+
commandContext.openRemoteView = () => { openView('remote'); };
|
|
204
|
+
commandContext.openSubscriptionView = () => { openView('subscription'); };
|
|
205
|
+
}
|