@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
|
@@ -8,8 +8,7 @@ import type { SelectionResult } from './selection-modal.ts';
|
|
|
8
8
|
|
|
9
9
|
export type ModalStackState = ActiveModalState & {
|
|
10
10
|
modalStack: string[];
|
|
11
|
-
modalReturnFocus?: 'prompt' | '
|
|
12
|
-
panelFocused: boolean;
|
|
11
|
+
modalReturnFocus?: 'prompt' | 'indicator';
|
|
13
12
|
indicatorFocused: boolean;
|
|
14
13
|
};
|
|
15
14
|
|
|
@@ -20,7 +19,7 @@ export function modalOpened(state: ModalStackState, name: string): void {
|
|
|
20
19
|
if (state.modalStack[state.modalStack.length - 1] === name) return;
|
|
21
20
|
if (state.modalStack.includes(name)) return;
|
|
22
21
|
if (state.modalStack.length === 0) {
|
|
23
|
-
state.modalReturnFocus = state.indicatorFocused ? 'indicator' :
|
|
22
|
+
state.modalReturnFocus = state.indicatorFocused ? 'indicator' : 'prompt';
|
|
24
23
|
}
|
|
25
24
|
state.modalStack.push(name);
|
|
26
25
|
}
|
|
@@ -49,10 +48,34 @@ export type EscapeState = ModalStackState & {
|
|
|
49
48
|
selectionModal: ModalStackState['selectionModal'];
|
|
50
49
|
autocompleteReset: () => void;
|
|
51
50
|
autocompleteUpdate?: (query: string) => void;
|
|
51
|
+
/**
|
|
52
|
+
* An agent or process view (core/session-focus.ts). With no modal open and
|
|
53
|
+
* the composer empty, Esc there goes back up one level (closing the
|
|
54
|
+
* process view's search first) and never reaches cancelGeneration: leaving
|
|
55
|
+
* a view never stops the agent, the process or main.
|
|
56
|
+
*/
|
|
57
|
+
sessionView?: { readonly active: boolean; escape(): void };
|
|
58
|
+
/**
|
|
59
|
+
* The main transcript's scroll. While it is scrolled away from the live
|
|
60
|
+
* bottom (and no modal, composer text or view took the Esc first), Esc
|
|
61
|
+
* returns to the bottom and re-locks there; it never reaches
|
|
62
|
+
* cancelGeneration then. At the bottom the next Esc interrupts as before.
|
|
63
|
+
*/
|
|
64
|
+
transcriptScroll?: TranscriptScrollControls;
|
|
65
|
+
/** Kit modals: the top one (or its own sub-level) is the first thing Esc pops. */
|
|
66
|
+
surfaceModals?: { readonly active: boolean; escape(): boolean };
|
|
52
67
|
clearOnboardingModelPickerCancelState?: () => void;
|
|
53
68
|
restoreOnboardingModelPickerCancelState?: () => void;
|
|
54
69
|
};
|
|
55
70
|
|
|
71
|
+
/** What handleEscape needs of the main transcript's scroll. */
|
|
72
|
+
export interface TranscriptScrollControls {
|
|
73
|
+
/** The main transcript is scrolled away from its live bottom. */
|
|
74
|
+
scrolledBack(): boolean;
|
|
75
|
+
/** Return to the live bottom and follow it again. */
|
|
76
|
+
toBottom(): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
56
79
|
export function handleEscape(state: EscapeState): {
|
|
57
80
|
prompt: string;
|
|
58
81
|
cursorPos: number;
|
|
@@ -62,7 +85,6 @@ export function handleEscape(state: EscapeState): {
|
|
|
62
85
|
shortcutsOverlayActive: boolean;
|
|
63
86
|
shortcutsScrollOffset: number;
|
|
64
87
|
selectionCallback: ((result: SelectionResult | null) => void) | null;
|
|
65
|
-
panelFocused: boolean;
|
|
66
88
|
indicatorFocused: boolean;
|
|
67
89
|
modalReturnFocus: NonNullable<ModalStackState['modalReturnFocus']>;
|
|
68
90
|
} {
|
|
@@ -74,7 +96,6 @@ export function handleEscape(state: EscapeState): {
|
|
|
74
96
|
let shortcutsOverlayActive = state.shortcutsOverlayActive;
|
|
75
97
|
let shortcutsScrollOffset = state.shortcutsScrollOffset;
|
|
76
98
|
let selectionCallback = state.selectionCallback;
|
|
77
|
-
let panelFocused = state.panelFocused;
|
|
78
99
|
let indicatorFocused = state.indicatorFocused;
|
|
79
100
|
let modalReturnFocus: NonNullable<ModalStackState['modalReturnFocus']> = state.modalReturnFocus ?? 'prompt';
|
|
80
101
|
|
|
@@ -85,12 +106,29 @@ export function handleEscape(state: EscapeState): {
|
|
|
85
106
|
shortcutsOverlayActive,
|
|
86
107
|
commandMode,
|
|
87
108
|
}) !== null) return;
|
|
88
|
-
panelFocused = modalReturnFocus === 'panel';
|
|
89
109
|
indicatorFocused = modalReturnFocus === 'indicator';
|
|
90
110
|
modalReturnFocus = 'prompt';
|
|
91
111
|
state.modalReturnFocus = 'prompt';
|
|
92
112
|
};
|
|
93
113
|
|
|
114
|
+
// Kit modals are always on top: one Esc pops one of their levels and stops.
|
|
115
|
+
if (state.surfaceModals?.active) {
|
|
116
|
+
state.surfaceModals.escape();
|
|
117
|
+
state.requestRender();
|
|
118
|
+
return {
|
|
119
|
+
prompt,
|
|
120
|
+
cursorPos,
|
|
121
|
+
commandMode,
|
|
122
|
+
helpOverlayActive,
|
|
123
|
+
helpScrollOffset,
|
|
124
|
+
shortcutsOverlayActive,
|
|
125
|
+
shortcutsScrollOffset,
|
|
126
|
+
selectionCallback,
|
|
127
|
+
indicatorFocused,
|
|
128
|
+
modalReturnFocus,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
94
132
|
if (state.settingsModal.active && state.settingsModal.editingMode) {
|
|
95
133
|
state.settingsModal.cancelEdit();
|
|
96
134
|
state.requestRender();
|
|
@@ -103,7 +141,6 @@ export function handleEscape(state: EscapeState): {
|
|
|
103
141
|
shortcutsOverlayActive,
|
|
104
142
|
shortcutsScrollOffset,
|
|
105
143
|
selectionCallback,
|
|
106
|
-
panelFocused,
|
|
107
144
|
indicatorFocused,
|
|
108
145
|
modalReturnFocus,
|
|
109
146
|
};
|
|
@@ -200,7 +237,6 @@ export function handleEscape(state: EscapeState): {
|
|
|
200
237
|
shortcutsOverlayActive,
|
|
201
238
|
shortcutsScrollOffset,
|
|
202
239
|
selectionCallback,
|
|
203
|
-
panelFocused,
|
|
204
240
|
indicatorFocused,
|
|
205
241
|
modalReturnFocus,
|
|
206
242
|
};
|
|
@@ -225,7 +261,6 @@ export function handleEscape(state: EscapeState): {
|
|
|
225
261
|
shortcutsOverlayActive,
|
|
226
262
|
shortcutsScrollOffset,
|
|
227
263
|
selectionCallback,
|
|
228
|
-
panelFocused,
|
|
229
264
|
indicatorFocused,
|
|
230
265
|
modalReturnFocus,
|
|
231
266
|
};
|
|
@@ -244,7 +279,43 @@ export function handleEscape(state: EscapeState): {
|
|
|
244
279
|
shortcutsOverlayActive,
|
|
245
280
|
shortcutsScrollOffset,
|
|
246
281
|
selectionCallback,
|
|
247
|
-
|
|
282
|
+
indicatorFocused,
|
|
283
|
+
modalReturnFocus,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Inside an agent or process view Esc goes up one level and stops here:
|
|
288
|
+
// interrupting belongs to main alone.
|
|
289
|
+
if (state.sessionView?.active) {
|
|
290
|
+
state.sessionView.escape();
|
|
291
|
+
state.requestRender();
|
|
292
|
+
return {
|
|
293
|
+
prompt,
|
|
294
|
+
cursorPos,
|
|
295
|
+
commandMode,
|
|
296
|
+
helpOverlayActive,
|
|
297
|
+
helpScrollOffset,
|
|
298
|
+
shortcutsOverlayActive,
|
|
299
|
+
shortcutsScrollOffset,
|
|
300
|
+
selectionCallback,
|
|
301
|
+
indicatorFocused,
|
|
302
|
+
modalReturnFocus,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Scrolled back: Esc returns to the live output and never interrupts the turn.
|
|
307
|
+
if (state.transcriptScroll?.scrolledBack()) {
|
|
308
|
+
state.transcriptScroll.toBottom();
|
|
309
|
+
state.requestRender();
|
|
310
|
+
return {
|
|
311
|
+
prompt,
|
|
312
|
+
cursorPos,
|
|
313
|
+
commandMode,
|
|
314
|
+
helpOverlayActive,
|
|
315
|
+
helpScrollOffset,
|
|
316
|
+
shortcutsOverlayActive,
|
|
317
|
+
shortcutsScrollOffset,
|
|
318
|
+
selectionCallback,
|
|
248
319
|
indicatorFocused,
|
|
249
320
|
modalReturnFocus,
|
|
250
321
|
};
|
|
@@ -260,7 +331,6 @@ export function handleEscape(state: EscapeState): {
|
|
|
260
331
|
shortcutsOverlayActive,
|
|
261
332
|
shortcutsScrollOffset,
|
|
262
333
|
selectionCallback,
|
|
263
|
-
panelFocused,
|
|
264
334
|
indicatorFocused,
|
|
265
335
|
modalReturnFocus,
|
|
266
336
|
};
|
|
@@ -12,6 +12,7 @@ import type { ModelPickerModal } from './model-picker.ts';
|
|
|
12
12
|
import { handleMcpWorkspaceToken, type McpWorkspace } from './mcp-workspace.ts';
|
|
13
13
|
import type { CommandContext } from './command-registry.ts';
|
|
14
14
|
import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
15
|
+
import type { OverlayFilters } from './overlay-filter.ts';
|
|
15
16
|
import type { FilePickerModal } from './file-picker.ts';
|
|
16
17
|
import type { BlockActionsMenu, BlockActionId } from '../renderer/block-actions.ts';
|
|
17
18
|
import type { SearchManager } from './search.ts';
|
|
@@ -64,6 +65,7 @@ export type ModalTokenRouteState = {
|
|
|
64
65
|
requestRender: () => void;
|
|
65
66
|
handleEscape: () => void;
|
|
66
67
|
contextInspectorModal: ContextInspectorModal;
|
|
68
|
+
overlayFilters?: OverlayFilters;
|
|
67
69
|
modalOpened: (name: string) => void;
|
|
68
70
|
filePicker: FilePickerModal;
|
|
69
71
|
imageRegistry: Map<string, { data: string; mediaType: string }>;
|
|
@@ -191,6 +193,7 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
191
193
|
helpScrollOffset: state.helpScrollOffset,
|
|
192
194
|
shortcutsOverlayActive: state.shortcutsOverlayActive,
|
|
193
195
|
shortcutsScrollOffset: state.shortcutsScrollOffset,
|
|
196
|
+
overlayFilters: state.overlayFilters,
|
|
194
197
|
requestRender: state.requestRender,
|
|
195
198
|
handleEscape: state.handleEscape,
|
|
196
199
|
};
|
|
@@ -241,6 +244,7 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
241
244
|
active: state.contextInspectorModal.active,
|
|
242
245
|
requestRender: state.requestRender,
|
|
243
246
|
handleEscape: state.handleEscape,
|
|
247
|
+
scroll: (delta) => state.contextInspectorModal.scrollBy(delta),
|
|
244
248
|
}, token)) {
|
|
245
249
|
return withState(state, true);
|
|
246
250
|
}
|
|
@@ -348,7 +348,6 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
|
|
|
348
348
|
}
|
|
349
349
|
if (handler.modalStack.length === 0) {
|
|
350
350
|
const returnFocus = handler.modalReturnFocus;
|
|
351
|
-
handler.panelFocused = returnFocus === 'panel';
|
|
352
351
|
handler.indicatorFocused = returnFocus === 'indicator';
|
|
353
352
|
handler.modalReturnFocus = 'prompt';
|
|
354
353
|
}
|
|
@@ -4,6 +4,7 @@ import type { CommandContext } from './command-registry.ts';
|
|
|
4
4
|
import type { CapabilityFilter, CategoryFilter, ModelPickerModal } from './model-picker.ts';
|
|
5
5
|
import { MODEL_PICKER_CHROME_LINES } from '../renderer/model-picker-overlay.ts';
|
|
6
6
|
import { ensureProviderKeyThenSelect } from './provider-key-intake.ts';
|
|
7
|
+
import { isTextBackspace } from './delete-key-policy.ts';
|
|
7
8
|
import { resolveAndValidatePath } from '@pellux/goodvibes-sdk/platform/utils';
|
|
8
9
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
9
10
|
import type { BlockActionId } from '../renderer/block-actions.ts';
|
|
@@ -49,17 +50,10 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
49
50
|
|
|
50
51
|
if (token.type === 'key') {
|
|
51
52
|
if (token.logicalName === 'escape') {
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// query
|
|
55
|
-
|
|
56
|
-
// freshly-opened, untouched picker where there is nothing to "clear".
|
|
57
|
-
// Clearing a non-empty query is still a distinct first Escape (whether
|
|
58
|
-
// or not search is focused); an empty query falls through to the same
|
|
59
|
-
// effort/contextCap/previousMode/close cascade as before.
|
|
60
|
-
if (state.modelPicker.mode !== 'contextCap' && state.modelPicker.query.length > 0) {
|
|
61
|
-
state.modelPicker.clearQuery();
|
|
62
|
-
} else if (state.modelPicker.mode === 'effort') {
|
|
53
|
+
// Esc pops exactly one level: the effort and context-cap steps and a
|
|
54
|
+
// provider-first model list are sub-levels of the picker; otherwise the
|
|
55
|
+
// picker closes. A typed query is not a level, it goes with the picker.
|
|
56
|
+
if (state.modelPicker.mode === 'effort') {
|
|
63
57
|
state.modelPicker.mode = 'model';
|
|
64
58
|
state.modelPicker.selectedIndex = 0;
|
|
65
59
|
} else if (state.modelPicker.mode === 'contextCap') {
|
|
@@ -73,11 +67,11 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
73
67
|
state.handleEscape();
|
|
74
68
|
return true;
|
|
75
69
|
}
|
|
76
|
-
} else if (token.logicalName
|
|
70
|
+
} else if (isTextBackspace(token.logicalName ?? '')) {
|
|
77
71
|
if (state.modelPicker.mode === 'contextCap') state.modelPicker.deleteContextCapChar();
|
|
78
|
-
else if (state.modelPicker.
|
|
72
|
+
else if (state.modelPicker.canFocusSearch()) state.modelPicker.deleteChar();
|
|
79
73
|
} else if (token.logicalName === 'enter') {
|
|
80
|
-
if (state.modelPicker.
|
|
74
|
+
if (state.modelPicker.focusRegion === 'targets') {
|
|
81
75
|
state.modelPicker.focusItems();
|
|
82
76
|
state.requestRender();
|
|
83
77
|
return true;
|
|
@@ -174,84 +168,45 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
174
168
|
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
175
169
|
}
|
|
176
170
|
}
|
|
177
|
-
} else if (token.logicalName === 'up') {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
state.requestRender();
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
if (state.modelPicker.canFocusSearch() && !state.modelPicker.searchFocused && state.modelPicker.selectedIndex === 0) {
|
|
184
|
-
state.modelPicker.focusSearch();
|
|
185
|
-
} else if (state.modelPicker.searchFocused) {
|
|
186
|
-
// Symmetric with 'down' (below): search now starts focused by default
|
|
187
|
-
// so 'up' must have a way out of it too, or it would be a
|
|
188
|
-
// silent no-op the very first time a user reaches for the list.
|
|
189
|
-
state.modelPicker.blurSearch();
|
|
190
|
-
} else {
|
|
191
|
-
const maxVis = Math.max(5, state.getViewportHeight() - MODEL_PICKER_CHROME_LINES - 4);
|
|
192
|
-
state.modelPicker.moveUp(maxVis);
|
|
193
|
-
}
|
|
194
|
-
} else if (token.logicalName === 'down') {
|
|
195
|
-
if (state.modelPicker.focusPane === 'targets') {
|
|
196
|
-
state.modelPicker.moveTarget(1);
|
|
197
|
-
state.requestRender();
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
if (state.modelPicker.searchFocused) {
|
|
201
|
-
state.modelPicker.blurSearch();
|
|
202
|
-
} else {
|
|
203
|
-
const maxVis = Math.max(5, state.getViewportHeight() - MODEL_PICKER_CHROME_LINES - 4);
|
|
204
|
-
state.modelPicker.moveDown(maxVis);
|
|
205
|
-
}
|
|
206
|
-
} else if (token.logicalName === 'left' && !state.modelPicker.searchFocused && state.modelPicker.mode !== 'contextCap') {
|
|
207
|
-
state.modelPicker.focusTargets();
|
|
208
|
-
} else if (token.logicalName === 'right' && !state.modelPicker.searchFocused && state.modelPicker.mode !== 'contextCap') {
|
|
171
|
+
} else if (token.logicalName === 'up' || token.logicalName === 'down') {
|
|
172
|
+
// The search row is always live, so arrows always walk the list.
|
|
173
|
+
const maxVis = Math.max(5, state.getViewportHeight() - MODEL_PICKER_CHROME_LINES - 4);
|
|
209
174
|
state.modelPicker.focusItems();
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
175
|
+
if (token.logicalName === 'up') state.modelPicker.moveUp(maxVis);
|
|
176
|
+
else state.modelPicker.moveDown(maxVis);
|
|
177
|
+
} else if ((token.logicalName === 'tab' || token.logicalName === 'right' || token.logicalName === 'left') && state.modelPicker.mode !== 'contextCap' && state.modelPicker.mode !== 'effort') {
|
|
178
|
+
// The five model targets are tabs: tab / → next, shift+tab / ← previous.
|
|
179
|
+
const back = token.logicalName === 'left' || (token.logicalName === 'tab' && token.shift);
|
|
180
|
+
state.modelPicker.moveTarget(back ? -1 : 1);
|
|
181
|
+
} else if (token.ctrl && state.modelPicker.mode === 'model') {
|
|
182
|
+
const picker = state.modelPicker;
|
|
183
|
+
if (token.logicalName === 't') {
|
|
214
184
|
const cycle: CategoryFilter[] = ['all', 'free', 'paid', 'subscription'];
|
|
215
|
-
|
|
216
|
-
|
|
185
|
+
picker.setCategoryFilter(cycle[(cycle.indexOf(picker.categoryFilter) + 1) % cycle.length]!);
|
|
186
|
+
} else if (token.logicalName === 'k') cycleCapabilityFilter(picker);
|
|
187
|
+
else if (token.logicalName === 'a') picker.toggleAvailableOnly();
|
|
188
|
+
else if (token.logicalName === 'b') picker.cycleBenchmarkSort();
|
|
189
|
+
else if (token.logicalName === 'g') picker.cycleGroupBy();
|
|
190
|
+
else if (token.logicalName === 'f') togglePin(state);
|
|
191
|
+
else if (token.logicalName === 'r') {
|
|
192
|
+
void state.commandContext?.executeCommand?.('refresh-models', []);
|
|
217
193
|
}
|
|
218
|
-
} else if (!state.modelPicker.searchFocused && token.logicalName === 'g' && state.modelPicker.mode === 'model') {
|
|
219
|
-
state.modelPicker.cycleGroupBy();
|
|
220
|
-
} else if (!state.modelPicker.searchFocused && token.logicalName === 'c' && state.modelPicker.mode === 'model') {
|
|
221
|
-
cycleCapabilityFilter(state.modelPicker);
|
|
222
|
-
} else if (!state.modelPicker.searchFocused && token.logicalName === 'a' && state.modelPicker.mode === 'model') {
|
|
223
|
-
state.modelPicker.toggleAvailableOnly();
|
|
224
|
-
} else if (!state.modelPicker.searchFocused && token.logicalName === 'b' && state.modelPicker.mode === 'model') {
|
|
225
|
-
state.modelPicker.cycleBenchmarkSort();
|
|
226
|
-
} else if (!state.modelPicker.searchFocused && token.logicalName === '/' && state.modelPicker.canFocusSearch()) {
|
|
227
|
-
state.modelPicker.focusItems();
|
|
228
|
-
state.modelPicker.focusSearch();
|
|
229
194
|
}
|
|
230
195
|
} else if (token.type === 'text') {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
//
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
238
|
-
} else if (token.value === ' ' && state.modelPicker.mode === 'model') {
|
|
239
|
-
const selected = state.modelPicker.getSelected();
|
|
240
|
-
if (selected && state.modelPicker.isLocalModel(selected)) state.modelPicker.enterContextCapMode(selected);
|
|
196
|
+
const picker = state.modelPicker;
|
|
197
|
+
if (picker.mode === 'contextCap') {
|
|
198
|
+
if (token.value.length === 1) picker.appendContextCapChar(token.value);
|
|
199
|
+
} else if (token.value === ' ' && picker.mode === 'model' && picker.query.length === 0) {
|
|
200
|
+
// Space on an untouched search sets a context cap for a local model.
|
|
201
|
+
const selected = picker.getSelected();
|
|
202
|
+
if (selected && picker.isLocalModel(selected)) picker.enterContextCapMode(selected);
|
|
241
203
|
} else if (token.value === '\t') {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
} else if (token.value === 'a' && state.modelPicker.mode === 'model') {
|
|
249
|
-
state.modelPicker.toggleAvailableOnly();
|
|
250
|
-
} else if (token.value === 'b' && state.modelPicker.mode === 'model') {
|
|
251
|
-
state.modelPicker.cycleBenchmarkSort();
|
|
252
|
-
} else if (token.value === '/' && state.modelPicker.canFocusSearch()) {
|
|
253
|
-
state.modelPicker.focusItems();
|
|
254
|
-
state.modelPicker.focusSearch();
|
|
204
|
+
picker.moveTarget(1);
|
|
205
|
+
} else if (picker.canFocusSearch() && !(token.value === '/' && picker.query.length === 0)) {
|
|
206
|
+
// Every printable character goes to the always-live search.
|
|
207
|
+
picker.focusItems();
|
|
208
|
+
picker.focusSearch();
|
|
209
|
+
for (const ch of token.value) if (ch >= ' ') picker.appendChar(ch);
|
|
255
210
|
}
|
|
256
211
|
}
|
|
257
212
|
|
|
@@ -259,6 +214,14 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
259
214
|
return true;
|
|
260
215
|
}
|
|
261
216
|
|
|
217
|
+
/** Pin or unpin the selected model: flipped locally at once, persisted through /pin or /unpin. */
|
|
218
|
+
function togglePin(state: ModelPickerRouteState): void {
|
|
219
|
+
const model = state.modelPicker.getSelected();
|
|
220
|
+
if (!model) return;
|
|
221
|
+
const pinned = state.modelPicker.togglePinnedLocal(model);
|
|
222
|
+
void state.commandContext?.executeCommand?.(pinned ? 'pin' : 'unpin', [model.registryKey]);
|
|
223
|
+
}
|
|
224
|
+
|
|
262
225
|
function cycleCapabilityFilter(modelPicker: ModelPickerModal): void {
|
|
263
226
|
const cycle: CapabilityFilter[] = ['none', 'reasoning', 'toolUse', 'multimodal'];
|
|
264
227
|
const cur = cycle.indexOf(modelPicker.capabilityFilter);
|
|
@@ -274,6 +237,8 @@ type EscapeOnlyModalRouteState = {
|
|
|
274
237
|
active: boolean;
|
|
275
238
|
requestRender: () => void;
|
|
276
239
|
handleEscape: () => void;
|
|
240
|
+
/** Optional scrolling for read-only modals: ↑ passes +1 (back), ↓ passes -1. */
|
|
241
|
+
scroll?: (delta: number) => void;
|
|
277
242
|
};
|
|
278
243
|
|
|
279
244
|
export function handleEscapeOnlyModalToken(state: EscapeOnlyModalRouteState, token: InputToken): boolean {
|
|
@@ -282,6 +247,12 @@ export function handleEscapeOnlyModalToken(state: EscapeOnlyModalRouteState, tok
|
|
|
282
247
|
state.handleEscape();
|
|
283
248
|
return true;
|
|
284
249
|
}
|
|
250
|
+
if (token.type === 'key' && state.scroll) {
|
|
251
|
+
if (token.logicalName === 'up') state.scroll(1);
|
|
252
|
+
else if (token.logicalName === 'down') state.scroll(-1);
|
|
253
|
+
else if (token.logicalName === 'pageup') state.scroll(10);
|
|
254
|
+
else if (token.logicalName === 'pagedown') state.scroll(-10);
|
|
255
|
+
}
|
|
285
256
|
state.requestRender();
|
|
286
257
|
return true;
|
|
287
258
|
}
|
|
@@ -319,21 +290,16 @@ type FilePickerRouteState = {
|
|
|
319
290
|
export function handleFilePickerToken(state: FilePickerRouteState, token: InputToken): boolean {
|
|
320
291
|
if (!state.filePicker.active) return false;
|
|
321
292
|
|
|
293
|
+
// The query is always live: typing filters, arrows move, Esc closes.
|
|
322
294
|
if (token.type === 'text') {
|
|
323
|
-
if (
|
|
324
|
-
state.filePicker.focusSearch();
|
|
325
|
-
} else if (state.filePicker.searchFocused && token.value === ' ' && state.filePicker.query === '') {
|
|
295
|
+
if (token.value === ' ' && state.filePicker.query === '') {
|
|
326
296
|
state.filePicker.close();
|
|
327
|
-
} else
|
|
297
|
+
} else {
|
|
298
|
+
state.filePicker.focusSearch();
|
|
328
299
|
state.filePicker.setQuery(state.filePicker.query + token.value);
|
|
329
300
|
}
|
|
330
301
|
} else if (token.type === 'key') {
|
|
331
302
|
if (token.logicalName === 'escape') {
|
|
332
|
-
if (state.filePicker.searchFocused && state.filePicker.query.length > 0) {
|
|
333
|
-
state.filePicker.setQuery('');
|
|
334
|
-
state.requestRender();
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
303
|
state.handleEscape();
|
|
338
304
|
return true;
|
|
339
305
|
} else if (token.logicalName === 'enter') {
|
|
@@ -379,21 +345,13 @@ export function handleFilePickerToken(state: FilePickerRouteState, token: InputT
|
|
|
379
345
|
}
|
|
380
346
|
state.filePicker.close();
|
|
381
347
|
} else if (token.logicalName === 'up') {
|
|
382
|
-
|
|
383
|
-
state.filePicker.focusSearch();
|
|
384
|
-
} else if (!state.filePicker.searchFocused) {
|
|
385
|
-
state.filePicker.moveUp();
|
|
386
|
-
}
|
|
348
|
+
state.filePicker.moveUp();
|
|
387
349
|
} else if (token.logicalName === 'down') {
|
|
388
|
-
|
|
389
|
-
state.filePicker.blurSearch();
|
|
390
|
-
} else {
|
|
391
|
-
state.filePicker.moveDown();
|
|
392
|
-
}
|
|
350
|
+
state.filePicker.moveDown();
|
|
393
351
|
} else if (token.logicalName === 'backspace') {
|
|
394
|
-
if (state.filePicker.
|
|
352
|
+
if (state.filePicker.query.length > 0) {
|
|
395
353
|
state.filePicker.setQuery(state.filePicker.query.slice(0, -1));
|
|
396
|
-
} else
|
|
354
|
+
} else {
|
|
397
355
|
const removeCount = state.filePicker.injectMode ? 2 : 1;
|
|
398
356
|
if (state.cursorPos >= removeCount) {
|
|
399
357
|
state.prompt = state.prompt.slice(0, state.cursorPos - removeCount) + state.prompt.slice(state.cursorPos);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* handler-session-view-route.ts, the keyboard while an agent or process is
|
|
3
|
+
* open full screen, and the session chips' Tab cycling everywhere.
|
|
4
|
+
*
|
|
5
|
+
* Runs after the modals (a modal open takes every key first) and before the
|
|
6
|
+
* work tree and the composer.
|
|
7
|
+
*
|
|
8
|
+
* Tab / Shift+Tab next / previous session chip, only while the composer
|
|
9
|
+
* is empty and not completing a command (otherwise Tab
|
|
10
|
+
* keeps its completion meaning and Shift+Tab its mode
|
|
11
|
+
* cycling), and only when the chips row shows
|
|
12
|
+
* ctrl+x stop the agent or process: the first press asks, the
|
|
13
|
+
* second (within 3 s) stops
|
|
14
|
+
* PageUp/PageDown scroll the view
|
|
15
|
+
*
|
|
16
|
+
* Agent view: Enter sends the composer's text to the agent as a steer (main
|
|
17
|
+
* keeps running); everything else types into the composer as usual.
|
|
18
|
+
*
|
|
19
|
+
* Process view: the composer takes no text (the process's stdin is not
|
|
20
|
+
* reachable), so letters are the view's keys: / search, n next match, y copy
|
|
21
|
+
* the output, r restart (when supported); ↑ ↓ scroll a row, End follows the
|
|
22
|
+
* output again.
|
|
23
|
+
*
|
|
24
|
+
* Esc is not handled here: it goes through the shared chain
|
|
25
|
+
* (handler-modal-stack.ts), whose view step goes back up one level and never
|
|
26
|
+
* cancels anything.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
|
|
30
|
+
import { isTextBackspace } from './delete-key-policy.ts';
|
|
31
|
+
|
|
32
|
+
/** What the route can do to the session views (shell/session-views.ts). */
|
|
33
|
+
export interface SessionViewControls {
|
|
34
|
+
/** 'main' when no view is open. */
|
|
35
|
+
readonly kind: 'main' | 'agent' | 'process';
|
|
36
|
+
readonly active: boolean;
|
|
37
|
+
/** The chips row is showing (more than one session to switch to). */
|
|
38
|
+
chipsVisible(): boolean;
|
|
39
|
+
cycle(delta: 1 | -1): void;
|
|
40
|
+
/** ctrl+x: ask on the first press, stop on the second. */
|
|
41
|
+
pressStop(): void;
|
|
42
|
+
scroll(rows: number): void;
|
|
43
|
+
/** Rows a page scroll moves. */
|
|
44
|
+
pageRows(): number;
|
|
45
|
+
/** Follow the newest output again (End). */
|
|
46
|
+
follow(): void;
|
|
47
|
+
/** Agent view: send text to the agent as a steer. True when it was accepted. */
|
|
48
|
+
steer(text: string): boolean;
|
|
49
|
+
/** Process view keys. */
|
|
50
|
+
readonly searchEditing: boolean;
|
|
51
|
+
startSearch(): void;
|
|
52
|
+
searchType(text: string): void;
|
|
53
|
+
searchBackspace(): void;
|
|
54
|
+
searchNext(): void;
|
|
55
|
+
copyOutput(): void;
|
|
56
|
+
restart(): void;
|
|
57
|
+
/** A key the process view does not take (the composer is off there). */
|
|
58
|
+
refuseText(): void;
|
|
59
|
+
/** Esc in a view: close the search, else go back up one level. */
|
|
60
|
+
escape(): void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SessionViewRouteState {
|
|
64
|
+
readonly controls: SessionViewControls | null;
|
|
65
|
+
prompt: string;
|
|
66
|
+
cursorPos: number;
|
|
67
|
+
readonly commandMode: boolean;
|
|
68
|
+
readonly saveUndoState: () => void;
|
|
69
|
+
readonly requestRender: () => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isBackTab(token: InputToken): boolean {
|
|
73
|
+
return token.type === 'key' && (token.logicalName === '\x1b[Z' || (token.logicalName === 'tab' && token.shift === true));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Returns true when the token was handled here. */
|
|
77
|
+
export function handleSessionViewToken(state: SessionViewRouteState, token: InputToken): boolean {
|
|
78
|
+
const c = state.controls;
|
|
79
|
+
if (!c) return false;
|
|
80
|
+
const composerIdle = state.prompt.length === 0 && !state.commandMode;
|
|
81
|
+
|
|
82
|
+
if (token.type === 'key' && composerIdle && !token.ctrl && !token.meta && (token.logicalName === 'tab' || isBackTab(token)) && c.chipsVisible()) {
|
|
83
|
+
c.cycle(isBackTab(token) ? -1 : 1);
|
|
84
|
+
state.requestRender();
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (!c.active) return false;
|
|
88
|
+
|
|
89
|
+
if (token.type === 'key') {
|
|
90
|
+
const name = token.logicalName ?? '';
|
|
91
|
+
if (token.ctrl && !token.meta && name === 'x') {
|
|
92
|
+
c.pressStop();
|
|
93
|
+
state.requestRender();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (!token.ctrl && !token.meta && (name === 'pageup' || name === 'pagedown')) {
|
|
97
|
+
c.scroll(name === 'pageup' ? c.pageRows() : -c.pageRows());
|
|
98
|
+
state.requestRender();
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (c.kind === 'agent' && name === 'enter' && !token.shift && !token.ctrl && !token.meta && !state.commandMode) {
|
|
102
|
+
const text = state.prompt.trim();
|
|
103
|
+
if (!text) return true; // an empty Enter in an agent view sends nothing (and never opens main's block actions)
|
|
104
|
+
if (c.steer(text)) {
|
|
105
|
+
state.saveUndoState();
|
|
106
|
+
state.prompt = '';
|
|
107
|
+
state.cursorPos = 0;
|
|
108
|
+
}
|
|
109
|
+
state.requestRender();
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (c.kind === 'process') return handleProcessKey(state, c, token, name);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (token.type === 'text' && c.kind === 'process') {
|
|
117
|
+
if (c.searchEditing) {
|
|
118
|
+
c.searchType(token.value.replace(/[\r\n]+/g, ' ').replace(/[\x00-\x1f\x7f]/g, ''));
|
|
119
|
+
} else if (token.value === '/') c.startSearch();
|
|
120
|
+
else if (token.value === 'n') c.searchNext();
|
|
121
|
+
else if (token.value === 'y') c.copyOutput();
|
|
122
|
+
else if (token.value === 'r') c.restart();
|
|
123
|
+
else if (token.value === 'G') c.follow();
|
|
124
|
+
else c.refuseText();
|
|
125
|
+
state.requestRender();
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function handleProcessKey(state: SessionViewRouteState, c: SessionViewControls, token: InputToken & { type: 'key' }, name: string): boolean {
|
|
132
|
+
if (token.ctrl || token.meta) return false;
|
|
133
|
+
if (c.searchEditing) {
|
|
134
|
+
if (name === 'enter') c.searchNext();
|
|
135
|
+
else if (isTextBackspace(name)) c.searchBackspace();
|
|
136
|
+
else return false;
|
|
137
|
+
state.requestRender();
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
switch (name) {
|
|
141
|
+
case 'up': c.scroll(1); break;
|
|
142
|
+
case 'down': c.scroll(-1); break;
|
|
143
|
+
case 'end': c.follow(); break;
|
|
144
|
+
case 'enter': break; // no stdin to send a line to
|
|
145
|
+
default: return false;
|
|
146
|
+
}
|
|
147
|
+
state.requestRender();
|
|
148
|
+
return true;
|
|
149
|
+
}
|