@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,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* changes-model.ts, the Changes modal's data: a unified diff split into files
|
|
3
|
+
* and hunks, every line numbered on both sides.
|
|
4
|
+
*
|
|
5
|
+
* Built on the review model's hunk parser (views/diff-review-model.ts) so the
|
|
6
|
+
* hunks the Changes modal navigates are the very ReviewHunk values the review
|
|
7
|
+
* comment and hunk-revert seams take. Each file keeps its own header lines so
|
|
8
|
+
* one hunk can be turned back into a patch git will apply on its own (staging
|
|
9
|
+
* a hunk is `git apply --cached` of exactly that patch).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { parseReviewDiff, type ReviewHunk } from '../views/diff-review-model.ts';
|
|
13
|
+
|
|
14
|
+
export type ChangeLineKind = 'add' | 'del' | 'ctx' | 'note';
|
|
15
|
+
|
|
16
|
+
export interface ChangeLine {
|
|
17
|
+
readonly kind: ChangeLineKind;
|
|
18
|
+
/** The line without its leading '+', '-' or ' '. */
|
|
19
|
+
readonly text: string;
|
|
20
|
+
readonly oldNo: number | null;
|
|
21
|
+
readonly newNo: number | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ChangeHunk {
|
|
25
|
+
readonly review: ReviewHunk;
|
|
26
|
+
readonly lines: readonly ChangeLine[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ChangeFile {
|
|
30
|
+
readonly path: string;
|
|
31
|
+
/** The file's diff header (diff --git, index, ---, +++ …), needed to apply one hunk alone. */
|
|
32
|
+
readonly headerLines: readonly string[];
|
|
33
|
+
readonly hunks: readonly ChangeHunk[];
|
|
34
|
+
readonly added: number;
|
|
35
|
+
readonly removed: number;
|
|
36
|
+
/** A binary file or a mode-only change: header only, nothing to show line by line. */
|
|
37
|
+
readonly headerOnly: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Why a file has no lines to show when it is not the usual binary or
|
|
40
|
+
* mode-only case (a large or empty untracked file). Read from a "# " line in
|
|
41
|
+
* the file's header, which changes-git.ts writes and git never does.
|
|
42
|
+
*/
|
|
43
|
+
readonly note?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function numberLines(hunk: ReviewHunk): ChangeLine[] {
|
|
47
|
+
const out: ChangeLine[] = [];
|
|
48
|
+
let oldNo = hunk.oldStart;
|
|
49
|
+
let newNo = hunk.newStart;
|
|
50
|
+
for (const raw of hunk.bodyLines) {
|
|
51
|
+
if (raw.startsWith('+')) out.push({ kind: 'add', text: raw.slice(1), oldNo: null, newNo: newNo++ });
|
|
52
|
+
else if (raw.startsWith('-')) out.push({ kind: 'del', text: raw.slice(1), oldNo: oldNo++, newNo: null });
|
|
53
|
+
else if (raw.startsWith('\\')) out.push({ kind: 'note', text: raw, oldNo: null, newNo: null });
|
|
54
|
+
else if (raw === '' ) continue; // the trailing split artifact of a chunk
|
|
55
|
+
else out.push({ kind: 'ctx', text: raw.slice(1), oldNo: oldNo++, newNo: newNo++ });
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Header lines (everything before the first hunk) of each `diff --git` chunk, in order. */
|
|
61
|
+
function chunkHeaders(raw: string): Array<{ lines: string[]; hasHunks: boolean }> {
|
|
62
|
+
const chunks = raw.split(/(?=^diff --git |^diff --cc |^diff --combined )/m).filter((chunk) => chunk.trim());
|
|
63
|
+
return chunks.map((chunk) => {
|
|
64
|
+
const lines = chunk.split('\n');
|
|
65
|
+
const first = lines.findIndex((line) => line.startsWith('@@'));
|
|
66
|
+
return { lines: (first < 0 ? lines : lines.slice(0, first)).filter((line) => line.length > 0), hasHunks: first >= 0 };
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function pathFromHeader(header: readonly string[]): string {
|
|
71
|
+
for (const line of header) {
|
|
72
|
+
const m = /^diff --git "?a\/(.+?)"? "?b\/(.+?)"?$/.exec(line);
|
|
73
|
+
if (m) return m[2]!;
|
|
74
|
+
}
|
|
75
|
+
for (const line of header) {
|
|
76
|
+
const m = /^\+\+\+ (?:b\/)?(.+)$/.exec(line);
|
|
77
|
+
if (m && m[1] !== '/dev/null') return m[1]!;
|
|
78
|
+
}
|
|
79
|
+
return 'unknown';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Split a unified diff (one or many files) into files and numbered hunks. */
|
|
83
|
+
export function parseChanges(raw: string): ChangeFile[] {
|
|
84
|
+
const headers = chunkHeaders(raw);
|
|
85
|
+
const reviewFiles = parseReviewDiff(raw);
|
|
86
|
+
const byIndex = new Map(reviewFiles.map((file) => [file.fileIndex, file]));
|
|
87
|
+
const files: ChangeFile[] = [];
|
|
88
|
+
headers.forEach((header, index) => {
|
|
89
|
+
const review = byIndex.get(index);
|
|
90
|
+
const hunks: ChangeHunk[] = (review?.hunks ?? []).map((hunk) => ({ review: hunk, lines: numberLines(hunk) }));
|
|
91
|
+
const noteLine = header.lines.find((line) => line.startsWith('# '));
|
|
92
|
+
files.push({
|
|
93
|
+
path: review?.filePath ?? pathFromHeader(header.lines),
|
|
94
|
+
headerLines: header.lines,
|
|
95
|
+
hunks,
|
|
96
|
+
added: hunks.reduce((n, h) => n + h.review.added, 0),
|
|
97
|
+
removed: hunks.reduce((n, h) => n + h.review.removed, 0),
|
|
98
|
+
headerOnly: hunks.length === 0,
|
|
99
|
+
...(noteLine ? { note: noteLine.slice(2) } : {}),
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** One hunk as a complete patch (file header + hunk) that `git apply` accepts on its own. */
|
|
106
|
+
export function hunkPatch(file: ChangeFile, hunk: ChangeHunk): string {
|
|
107
|
+
const header = file.headerLines.filter((line) => !line.startsWith('similarity') && !line.startsWith('rename'));
|
|
108
|
+
const hasFileLines = header.some((line) => line.startsWith('--- ')) && header.some((line) => line.startsWith('+++ '));
|
|
109
|
+
const fileLines = hasFileLines ? header : [...header, `--- a/${file.path}`, `+++ b/${file.path}`];
|
|
110
|
+
return [...fileLines, hunk.review.header, ...hunk.review.bodyLines.filter((line, i, all) => !(line === '' && i === all.length - 1))].join('\n') + '\n';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The language fence tag for a path (its extension), for syntax colors. */
|
|
114
|
+
export function languageForPath(path: string): string {
|
|
115
|
+
const base = path.split('/').pop() ?? path;
|
|
116
|
+
if (base === 'Dockerfile') return 'dockerfile';
|
|
117
|
+
if (base === 'Makefile') return 'make';
|
|
118
|
+
const dot = base.lastIndexOf('.');
|
|
119
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : '';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Total +/- across files. */
|
|
123
|
+
export function changeTotals(files: readonly ChangeFile[]): { added: number; removed: number } {
|
|
124
|
+
return files.reduce((acc, f) => ({ added: acc.added + f.added, removed: acc.removed + f.removed }), { added: 0, removed: 0 });
|
|
125
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* command-palette-types.ts, the shapes the command palette and its renderer
|
|
3
|
+
* share (kept apart so the renderer never imports the modal it draws).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type PaletteGroup = 'Suggested' | 'Session' | 'Views' | 'Settings' | 'Commands';
|
|
7
|
+
|
|
8
|
+
export interface PaletteEntry {
|
|
9
|
+
/** The command's primary name. */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Human title for the row. */
|
|
12
|
+
readonly title: string;
|
|
13
|
+
readonly description: string;
|
|
14
|
+
readonly category: string;
|
|
15
|
+
readonly group: Exclude<PaletteGroup, 'Suggested'>;
|
|
16
|
+
readonly aliases: readonly string[];
|
|
17
|
+
/** Extra search words (former view names and the like). */
|
|
18
|
+
readonly keywords: readonly string[];
|
|
19
|
+
readonly argsHint?: string;
|
|
20
|
+
/** True when the command has required arguments: Enter fills the composer. */
|
|
21
|
+
readonly needsArgs: boolean;
|
|
22
|
+
/** Right column; defaults to the slash command. */
|
|
23
|
+
readonly right?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type PaletteRunMode = 'run' | 'fill';
|
|
27
|
+
|
|
28
|
+
export interface PaletteSection {
|
|
29
|
+
readonly group: PaletteGroup;
|
|
30
|
+
readonly entries: readonly PaletteEntry[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** What the palette renderer reads from the palette. */
|
|
34
|
+
export interface PaletteView {
|
|
35
|
+
readonly query: string;
|
|
36
|
+
readonly entries: readonly PaletteEntry[];
|
|
37
|
+
sections(): PaletteSection[];
|
|
38
|
+
flat(): PaletteEntry[];
|
|
39
|
+
getSelected(): PaletteEntry | null;
|
|
40
|
+
describe(id: string): string | undefined;
|
|
41
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* command-palette.ts, the command palette (ctrl+p, also ctrl+k and /palette).
|
|
3
|
+
*
|
|
4
|
+
* One entry point for everything: every registered slash command, built live
|
|
5
|
+
* from the registry so it can never drift from the real command set. With no
|
|
6
|
+
* query the palette shows four curated groups (Suggested, Session, Views,
|
|
7
|
+
* Settings) and then every other command; typing filters across names,
|
|
8
|
+
* aliases, titles, descriptions, categories and search words (former view names
|
|
9
|
+
* such as "fleet", "cockpit", "tokens" and "git" find the modal that holds
|
|
10
|
+
* them now: Agents, Usage, Changes, Notifications). The slash
|
|
11
|
+
* command sits right-aligned on every row, so the palette also teaches it.
|
|
12
|
+
*
|
|
13
|
+
* Suggested learns from what you use (per process); until it has something
|
|
14
|
+
* to learn from it offers a sensible starting set.
|
|
15
|
+
*
|
|
16
|
+
* Enter runs the selected command; a command that needs arguments (and tab,
|
|
17
|
+
* for any command) fills the composer instead so the arguments can be typed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
|
|
21
|
+
import type { SlashCommand } from './command-registry.ts';
|
|
22
|
+
import type { SurfaceModal, SurfaceModalHost } from './surface-modal-host.ts';
|
|
23
|
+
import type { SurfaceLayer } from '../renderer/surface-kit.ts';
|
|
24
|
+
import { renderCommandPalette } from '../renderer/command-palette.ts';
|
|
25
|
+
import { isTextBackspace } from './delete-key-policy.ts';
|
|
26
|
+
import { AGENT_VIEW_NAMES, CHANGES_VIEW_NAMES, USAGE_VIEW_NAMES } from './views.ts';
|
|
27
|
+
import type { PaletteEntry, PaletteGroup, PaletteRunMode, PaletteSection, PaletteView } from './command-palette-types.ts';
|
|
28
|
+
export type { PaletteEntry, PaletteGroup, PaletteRunMode, PaletteSection } from './command-palette-types.ts';
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
interface CuratedEntry {
|
|
33
|
+
readonly title: string;
|
|
34
|
+
readonly group: Exclude<PaletteGroup, 'Suggested' | 'Commands'>;
|
|
35
|
+
readonly keywords?: readonly string[];
|
|
36
|
+
readonly right?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The curated groups, by command name. A name that is not registered in this
|
|
41
|
+
* build is skipped, so the table never shows a command that does not exist.
|
|
42
|
+
*/
|
|
43
|
+
const CURATED: ReadonlyArray<readonly [string, CuratedEntry]> = [
|
|
44
|
+
['resume', { title: 'Resume a session', group: 'Session', keywords: ['open', 'load'] }],
|
|
45
|
+
['session', { title: 'Sessions', group: 'Session', keywords: ['saved', 'hosted', 'history'] }],
|
|
46
|
+
['clear', { title: 'Clear the conversation', group: 'Session', keywords: ['new', 'reset'] }],
|
|
47
|
+
['compact', { title: 'Compact context', group: 'Session', keywords: ['summarize', 'tokens'] }],
|
|
48
|
+
['fork', { title: 'Fork from here', group: 'Session', keywords: ['branch'] }],
|
|
49
|
+
['bookmarks', { title: 'Bookmarks', group: 'Session' }],
|
|
50
|
+
['replay', { title: 'Replay a session', group: 'Session' }],
|
|
51
|
+
['agents', { title: 'Agents', group: 'Views', keywords: [...AGENT_VIEW_NAMES, 'hosted', 'acp', 'claude code', 'codex', 'opencode', 'steer', 'stop'] }],
|
|
52
|
+
['usage', { title: 'Usage', group: 'Views', keywords: [...USAGE_VIEW_NAMES, 'budget', 'compact', 'price'] }],
|
|
53
|
+
['changes', { title: 'Changes', group: 'Views', keywords: [...CHANGES_VIEW_NAMES, 'files', 'stage', 'commit', 'hunk'] }],
|
|
54
|
+
['notifications', { title: 'Notifications', group: 'Views', keywords: ['notifications', 'alerts', 'messages', 'history', 'toasts'] }],
|
|
55
|
+
['context', { title: 'Context inspector', group: 'Views', keywords: ['tokens', 'window'] }],
|
|
56
|
+
['work-plan', { title: 'Work plan', group: 'Views', keywords: ['todo', 'plan'] }],
|
|
57
|
+
['hosted', { title: 'Hosted sessions', group: 'Views', keywords: ['daemon'] }],
|
|
58
|
+
['health', { title: 'Health', group: 'Views', keywords: ['doctor'] }],
|
|
59
|
+
['settings', { title: 'Settings', group: 'Settings', keywords: ['preferences', 'options', 'theme'] }],
|
|
60
|
+
['model', { title: 'Switch model', group: 'Settings', keywords: ['provider', 'llm'] }],
|
|
61
|
+
['effort', { title: 'Reasoning effort', group: 'Settings', keywords: ['thinking'] }],
|
|
62
|
+
['shortcuts', { title: 'Keyboard shortcuts', group: 'Settings', keywords: ['keys', 'help'], right: '?' }],
|
|
63
|
+
['keybindings', { title: 'Keybindings', group: 'Settings', keywords: ['keys'] }],
|
|
64
|
+
['mode', { title: 'Approval mode', group: 'Settings', keywords: ['permissions', 'hitl'] }],
|
|
65
|
+
['profiles', { title: 'Profiles', group: 'Settings' }],
|
|
66
|
+
['local-auth', { title: 'Local accounts', group: 'Settings', keywords: ['accounts', 'users', 'auth', 'password', 'local-auth'] }],
|
|
67
|
+
['tts', { title: 'Voice and speech', group: 'Settings', keywords: ['voice'] }],
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/** The Suggested group before there is any usage to learn from. */
|
|
71
|
+
const DEFAULT_SUGGESTED = ['changes', 'model', 'compact'];
|
|
72
|
+
const SUGGESTED_COUNT = 3;
|
|
73
|
+
|
|
74
|
+
/** How often each command was run from the palette in this process. */
|
|
75
|
+
const usage = new Map<string, number>();
|
|
76
|
+
|
|
77
|
+
export function recordPaletteUse(id: string): void {
|
|
78
|
+
usage.set(id, (usage.get(id) ?? 0) + 1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Test hook. */
|
|
82
|
+
export function resetPaletteUsageForTests(): void {
|
|
83
|
+
usage.clear();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function hasRequiredArgs(command: SlashCommand): boolean {
|
|
87
|
+
const hint = command.argsHint ?? command.usage ?? '';
|
|
88
|
+
return /<[^>]+>/.test(hint.replace(/\[[^\]]*\]/g, ''));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Title for a command outside the curated table: its description's first clause. */
|
|
92
|
+
function titleFromDescription(command: SlashCommand): string {
|
|
93
|
+
const text = command.description.trim();
|
|
94
|
+
if (!text) return `/${command.name}`;
|
|
95
|
+
const first = text.split(/(?<=[.;])\s/)[0]!.replace(/[.;]$/, '');
|
|
96
|
+
return first.length > 0 ? first : text;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Build the palette's entries from the registered commands. */
|
|
100
|
+
export function buildPaletteEntries(commands: readonly SlashCommand[], categories: ReadonlyMap<string, string>): PaletteEntry[] {
|
|
101
|
+
const curated = new Map(CURATED);
|
|
102
|
+
const entries: PaletteEntry[] = [];
|
|
103
|
+
for (const command of commands) {
|
|
104
|
+
const c = curated.get(command.name);
|
|
105
|
+
entries.push({
|
|
106
|
+
id: command.name,
|
|
107
|
+
title: c?.title ?? titleFromDescription(command),
|
|
108
|
+
description: command.description,
|
|
109
|
+
category: categories.get(command.name) ?? 'Other',
|
|
110
|
+
group: c?.group ?? 'Commands',
|
|
111
|
+
aliases: command.aliases ?? [],
|
|
112
|
+
keywords: c?.keywords ?? [],
|
|
113
|
+
argsHint: command.argsHint ?? command.usage,
|
|
114
|
+
needsArgs: hasRequiredArgs(command),
|
|
115
|
+
right: c?.right,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Curated entries in table order, the rest alphabetically by name.
|
|
119
|
+
const order = new Map(CURATED.map(([name], index) => [name, index]));
|
|
120
|
+
return entries.sort((a, b) => {
|
|
121
|
+
const oa = order.get(a.id);
|
|
122
|
+
const ob = order.get(b.id);
|
|
123
|
+
if (oa !== undefined && ob !== undefined) return oa - ob;
|
|
124
|
+
if (oa !== undefined) return -1;
|
|
125
|
+
if (ob !== undefined) return 1;
|
|
126
|
+
return a.id.localeCompare(b.id);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Matching
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
function wordScore(entry: PaletteEntry, word: string): number {
|
|
135
|
+
const name = entry.id.toLowerCase();
|
|
136
|
+
const title = entry.title.toLowerCase();
|
|
137
|
+
if (name === word) return 100;
|
|
138
|
+
if (name.startsWith(word)) return 80;
|
|
139
|
+
if (entry.aliases.some((a) => a.toLowerCase() === word)) return 75;
|
|
140
|
+
if (entry.aliases.some((a) => a.toLowerCase().startsWith(word))) return 70;
|
|
141
|
+
if (title.split(/[\s/-]+/).some((w) => w.startsWith(word))) return 60;
|
|
142
|
+
if (entry.keywords.some((k) => k.toLowerCase().startsWith(word))) return 55;
|
|
143
|
+
if (name.includes(word) || title.includes(word)) return 40;
|
|
144
|
+
if (entry.keywords.some((k) => k.toLowerCase().includes(word))) return 35;
|
|
145
|
+
if (entry.description.toLowerCase().includes(word)) return 20;
|
|
146
|
+
if (entry.category.toLowerCase().includes(word)) return 10;
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Match score for a query (every word must match somewhere), or 0 for no match. */
|
|
151
|
+
export function paletteScore(entry: PaletteEntry, query: string): number {
|
|
152
|
+
const words = query.trim().toLowerCase().replace(/^\//, '').split(/\s+/).filter(Boolean);
|
|
153
|
+
if (words.length === 0) return 1;
|
|
154
|
+
let total = 0;
|
|
155
|
+
for (const word of words) {
|
|
156
|
+
const s = wordScore(entry, word);
|
|
157
|
+
if (s === 0) return 0;
|
|
158
|
+
total += s;
|
|
159
|
+
}
|
|
160
|
+
return total;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// The modal
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
export interface CommandPaletteOptions {
|
|
169
|
+
readonly entries: readonly PaletteEntry[];
|
|
170
|
+
/** Run or fill the composer with a command; the palette has already closed. */
|
|
171
|
+
readonly onRun: (entry: PaletteEntry, mode: PaletteRunMode) => void;
|
|
172
|
+
/** Optional live note for a row (e.g. the current model), shown muted after its title. */
|
|
173
|
+
readonly describe?: (id: string) => string | undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class CommandPalette implements SurfaceModal, PaletteView {
|
|
177
|
+
readonly name = 'command-palette';
|
|
178
|
+
query = '';
|
|
179
|
+
selectedIndex = 0;
|
|
180
|
+
private sectionsCache: { query: string; sections: PaletteSection[] } | null = null;
|
|
181
|
+
|
|
182
|
+
constructor(private readonly options: CommandPaletteOptions) {}
|
|
183
|
+
|
|
184
|
+
get entries(): readonly PaletteEntry[] {
|
|
185
|
+
return this.options.entries;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
describe(id: string): string | undefined {
|
|
189
|
+
return this.options.describe?.(id);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The groups shown for the current query. */
|
|
193
|
+
sections(): PaletteSection[] {
|
|
194
|
+
if (this.sectionsCache?.query === this.query) return this.sectionsCache.sections;
|
|
195
|
+
const sections: PaletteSection[] = [];
|
|
196
|
+
if (this.query.trim().length === 0) {
|
|
197
|
+
const byId = new Map(this.entries.map((e) => [e.id, e]));
|
|
198
|
+
const learned = [...usage.entries()].sort((a, b) => b[1] - a[1]).map(([id]) => id);
|
|
199
|
+
const suggested: PaletteEntry[] = [];
|
|
200
|
+
for (const id of [...learned, ...DEFAULT_SUGGESTED]) {
|
|
201
|
+
const entry = byId.get(id);
|
|
202
|
+
if (entry && !suggested.includes(entry)) suggested.push(entry);
|
|
203
|
+
if (suggested.length >= SUGGESTED_COUNT) break;
|
|
204
|
+
}
|
|
205
|
+
if (suggested.length > 0) sections.push({ group: 'Suggested', entries: suggested });
|
|
206
|
+
for (const group of ['Session', 'Views', 'Settings', 'Commands'] as const) {
|
|
207
|
+
const entries = this.entries.filter((e) => e.group === group && !suggested.includes(e));
|
|
208
|
+
if (entries.length > 0) sections.push({ group, entries });
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
const ranked = this.entries
|
|
212
|
+
.map((entry, index) => ({ entry, index, score: paletteScore(entry, this.query) }))
|
|
213
|
+
.filter((r) => r.score > 0)
|
|
214
|
+
.sort((a, b) => b.score - a.score || a.index - b.index)
|
|
215
|
+
.map((r) => r.entry);
|
|
216
|
+
if (ranked.length > 0) sections.push({ group: 'Commands', entries: ranked });
|
|
217
|
+
}
|
|
218
|
+
this.sectionsCache = { query: this.query, sections };
|
|
219
|
+
return sections;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Entries in display order (what up and down walk). */
|
|
223
|
+
flat(): PaletteEntry[] {
|
|
224
|
+
return this.sections().flatMap((s) => s.entries);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
getSelected(): PaletteEntry | null {
|
|
228
|
+
const flat = this.flat();
|
|
229
|
+
return flat[Math.max(0, Math.min(this.selectedIndex, flat.length - 1))] ?? null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
setQuery(query: string): void {
|
|
233
|
+
this.query = query;
|
|
234
|
+
this.selectedIndex = 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
move(delta: number): void {
|
|
238
|
+
const count = this.flat().length;
|
|
239
|
+
if (count === 0) return;
|
|
240
|
+
this.selectedIndex = (this.selectedIndex + delta + count) % count;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
handleToken(token: InputToken, host: SurfaceModalHost): void {
|
|
244
|
+
if (token.type === 'text') {
|
|
245
|
+
const text = [...token.value].filter((ch) => ch >= ' ' && ch !== '\x7f').join('');
|
|
246
|
+
if (text) this.setQuery(this.query + text);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (token.type !== 'key') return;
|
|
250
|
+
const key = token.logicalName ?? '';
|
|
251
|
+
if (token.ctrl && (key === 'p' || key === 'k')) {
|
|
252
|
+
host.close(this, 'escape');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (key === 'up') this.move(-1);
|
|
256
|
+
else if (key === 'down') this.move(1);
|
|
257
|
+
else if (key === 'pageup') this.move(-10);
|
|
258
|
+
else if (key === 'pagedown') this.move(10);
|
|
259
|
+
else if (isTextBackspace(key)) this.setQuery(this.query.slice(0, -1));
|
|
260
|
+
else if (key === 'enter' || key === 'tab') {
|
|
261
|
+
const entry = this.getSelected();
|
|
262
|
+
if (!entry) return;
|
|
263
|
+
host.close(this, 'done');
|
|
264
|
+
recordPaletteUse(entry.id);
|
|
265
|
+
this.options.onRun(entry, key === 'tab' || entry.needsArgs ? 'fill' : 'run');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
render(screenWidth: number, screenHeight: number): SurfaceLayer {
|
|
270
|
+
return renderCommandPalette(this, screenWidth, screenHeight);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -9,7 +9,7 @@ import type { SelectionItem, SelectionResult, SelectionAction } from './selectio
|
|
|
9
9
|
import type { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state';
|
|
10
10
|
import type { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
|
|
11
11
|
import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
12
|
-
import type {
|
|
12
|
+
import type { ViewTarget } from './views.ts';
|
|
13
13
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
14
14
|
import type { OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
15
15
|
import type { OpenOnboardingWizardOptions } from './handler-ui-state.ts';
|
|
@@ -119,7 +119,7 @@ export interface CommandUiActions {
|
|
|
119
119
|
/** Set the daemon-held keep-awake toggle (power.keepAwake.set), acquiring/releasing the inhibitor live; returns the new state. */
|
|
120
120
|
setKeepAwake?: (enabled: boolean) => Promise<import('../core/power-status.ts').PowerSurfaceState>;
|
|
121
121
|
/** True while an LLM turn is actively streaming. Used to give Escape
|
|
122
|
-
* cancel-turn precedence over a focused
|
|
122
|
+
* cancel-turn precedence over a focused modal's own escape handling. */
|
|
123
123
|
isGenerating?: () => boolean;
|
|
124
124
|
completeModelSelection?: (selection: {
|
|
125
125
|
model: { id: string; provider: string; displayName: string; registryKey: string };
|
|
@@ -180,13 +180,20 @@ export interface CommandShellUiOpeners {
|
|
|
180
180
|
openSelection?: (
|
|
181
181
|
title: string,
|
|
182
182
|
items: SelectionItem[],
|
|
183
|
-
opts: {
|
|
183
|
+
opts: {
|
|
184
|
+
preSelectId?: string;
|
|
185
|
+
allowSearch?: boolean;
|
|
186
|
+
customActions?: Map<string, SelectionAction>;
|
|
187
|
+
primaryVerbLabel?: string;
|
|
188
|
+
/** Live preview: called whenever the highlighted row changes. */
|
|
189
|
+
onHighlight?: (item: SelectionItem | null) => void;
|
|
190
|
+
} | undefined,
|
|
184
191
|
callback: (result: SelectionResult | null) => void,
|
|
185
192
|
) => void;
|
|
186
193
|
openSettingsModal?: (target?: string) => void;
|
|
187
194
|
/**
|
|
188
195
|
* Open a MIGRATE-TO-MODAL surface by name (purge skeleton, B
|
|
189
|
-
* command runtimes call this instead of
|
|
196
|
+
* command runtimes call this instead of an older view opener once their view
|
|
190
197
|
* is converted to a ModalFactory config). Threaded from ui-openers.ts the
|
|
191
198
|
* same way openSettingsModal is.
|
|
192
199
|
*/
|
|
@@ -195,13 +202,26 @@ export interface CommandShellUiOpeners {
|
|
|
195
202
|
openProfilePicker?: () => void;
|
|
196
203
|
openShortcutsOverlay?: () => void;
|
|
197
204
|
getScrollTop?: () => number;
|
|
198
|
-
openPanelPicker?: () => void;
|
|
199
205
|
/**
|
|
200
206
|
* Open the fuzzy command palette, a searchable picker over every registered
|
|
201
207
|
* slash command (generated live from the registry). Bound to Ctrl+K and the
|
|
202
208
|
* /palette command. Selecting a command pre-fills it into the composer.
|
|
203
209
|
*/
|
|
204
210
|
openCommandPalette?: () => void;
|
|
211
|
+
/**
|
|
212
|
+
* Ask a yes/no question in a small centered confirm dialog (a kit modal on
|
|
213
|
+
* top of whatever is open). Resolves true only when the confirming button
|
|
214
|
+
* is pressed; Esc and "no" resolve false.
|
|
215
|
+
*/
|
|
216
|
+
confirm?: (options: import('./confirm-dialog.ts').ConfirmOptions) => Promise<boolean>;
|
|
217
|
+
/** Show a toast in the top right corner for five seconds (a colored ┃ on both sides). */
|
|
218
|
+
showToast?: (toast: import('../renderer/surface-kit-parts.ts').ToastSpec) => void;
|
|
219
|
+
/**
|
|
220
|
+
* The /status report: session, model, tools, notification mode, token
|
|
221
|
+
* totals and cost, context use, cross-surface posture and the safety
|
|
222
|
+
* states. Built by the shell from the same sources the footer reads.
|
|
223
|
+
*/
|
|
224
|
+
describeStatus?: () => string;
|
|
205
225
|
/**
|
|
206
226
|
* Command name -> reference-category label, from the same single source of
|
|
207
227
|
* truth as the generated command reference (categorizeBuiltinCommands),
|
|
@@ -212,40 +232,63 @@ export interface CommandShellUiOpeners {
|
|
|
212
232
|
*/
|
|
213
233
|
getCommandCategories?: () => ReadonlyMap<string, string>;
|
|
214
234
|
/**
|
|
215
|
-
* Open
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
235
|
+
* Open the modal that holds a view, by its current or old name ('agents',
|
|
236
|
+
* 'fleet', 'cockpit', 'tokens', 'git', 'providers', …; see views.ts).
|
|
237
|
+
* `target` selects a specific process in the Agents modal. Returns false
|
|
238
|
+
* when nothing holds that name.
|
|
239
|
+
*/
|
|
240
|
+
openView?: (name: string, target?: ViewTarget) => boolean;
|
|
241
|
+
/** The Agents modal; `target` selects a process, `hosted` opens the attached hosted session. */
|
|
242
|
+
openAgents?: (options?: { readonly target?: ViewTarget; readonly hosted?: boolean }) => void;
|
|
243
|
+
/**
|
|
244
|
+
* Open an agent or a background process full screen (core/session-focus.ts).
|
|
245
|
+
* False when it is not known here (the caller keeps its own fallback).
|
|
246
|
+
*/
|
|
247
|
+
openSessionView?: (target: { readonly kind: 'agent' | 'process'; readonly id: string }) => boolean;
|
|
248
|
+
/** The Usage modal (`tab: 'agents'` opens on the per-agent cost ledger). */
|
|
249
|
+
openUsage?: (options?: { readonly tab?: 'overview' | 'turns' | 'agents' }) => void;
|
|
250
|
+
/** The Changes modal over the repository (this session's files, not staged, staged or vs HEAD). */
|
|
251
|
+
openChanges?: (options?: { readonly source?: import('./changes-git.ts').ChangesSource }) => void;
|
|
252
|
+
/**
|
|
253
|
+
* Show a diff someone else produced (a rewind checkpoint, a workstream
|
|
254
|
+
* attempt, a fleet candidate) in a read-only Changes preview. With a
|
|
255
|
+
* question, resolves true only when its confirm button is pressed; closing
|
|
256
|
+
* the preview answers no. `note` is shown when there is no diff to draw.
|
|
222
257
|
*/
|
|
223
|
-
|
|
224
|
-
|
|
258
|
+
previewChanges?: (options: {
|
|
259
|
+
readonly title: string;
|
|
260
|
+
readonly diff?: string;
|
|
261
|
+
readonly note?: string;
|
|
262
|
+
readonly question?: import('./changes-modal.ts').ChangesQuestionSpec;
|
|
263
|
+
}) => Promise<boolean>;
|
|
264
|
+
/** The notification history. */
|
|
265
|
+
openNotifications?: () => void;
|
|
266
|
+
/** Open a file in $VISUAL / $EDITOR (at a line when the editor takes one). */
|
|
267
|
+
openFileInEditor?: (path: string, line?: number) => void;
|
|
225
268
|
focusPrompt?: () => void;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
269
|
+
openOpsView?: () => void;
|
|
270
|
+
openCockpitView?: () => void;
|
|
271
|
+
openOrchestrationView?: () => void;
|
|
272
|
+
openForensicsView?: () => void;
|
|
273
|
+
openIncidentView?: () => void;
|
|
274
|
+
openPolicyView?: () => void;
|
|
275
|
+
openHooksView?: () => void;
|
|
276
|
+
openCommunicationView?: () => void;
|
|
234
277
|
openMcpWorkspace?: () => void;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
278
|
+
openSecurityView?: () => void;
|
|
279
|
+
openKnowledgeView?: () => void;
|
|
280
|
+
openMemoryView?: () => void;
|
|
281
|
+
openRemoteView?: () => void;
|
|
282
|
+
openSubscriptionView?: () => void;
|
|
240
283
|
/**
|
|
241
|
-
* Open the
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
284
|
+
* Open the local-auth password prompt (a kit modal) for the operation and
|
|
285
|
+
* user. Keystrokes land in a private buffer that is never drawn; no
|
|
286
|
+
* plaintext password reaches input history, the transcript, logs, or
|
|
287
|
+
* recovery files. An empty username for add-user asks for it first.
|
|
245
288
|
*/
|
|
246
289
|
openLocalAuthMaskedEntry?: (
|
|
247
290
|
kind: 'add-user' | 'rotate-password',
|
|
248
|
-
username
|
|
291
|
+
username?: string,
|
|
249
292
|
) => void;
|
|
250
293
|
}
|
|
251
294
|
|
|
@@ -310,7 +353,6 @@ export interface CommandWorkspaceUiServices {
|
|
|
310
353
|
gatewayMethods?: GatewayMethodCatalog;
|
|
311
354
|
workspaceTrustManager?: import('@pellux/goodvibes-sdk/platform/runtime/operations').WorkspaceTrustManager;
|
|
312
355
|
workspaceRegistrationManager?: import('@pellux/goodvibes-sdk/platform/runtime/operations').WorkspaceRegistrationManager;
|
|
313
|
-
panelManager?: PanelManager;
|
|
314
356
|
profileManager?: import('@pellux/goodvibes-sdk/platform/profiles').ProfileManager;
|
|
315
357
|
bookmarkManager?: import('@pellux/goodvibes-sdk/platform/bookmarks').BookmarkManager;
|
|
316
358
|
projectPlanningService?: import('@pellux/goodvibes-sdk/platform/knowledge').ProjectPlanningService;
|
|
@@ -350,7 +392,7 @@ export interface CommandOpsServices
|
|
|
350
392
|
export interface CommandExtensionRegistryServices {
|
|
351
393
|
readonly toolRegistry: ToolRegistry;
|
|
352
394
|
readonly mcpRegistry: McpRegistry;
|
|
353
|
-
readonly evalRegistry?: import('../
|
|
395
|
+
readonly evalRegistry?: import('../views/eval-registry.ts').EvalRegistry;
|
|
354
396
|
}
|
|
355
397
|
|
|
356
398
|
export interface CommandExtensionServices
|
|
@@ -408,7 +450,7 @@ export interface CommandContext
|
|
|
408
450
|
* unaffected, fuzzyMatch still searches every registered command exactly as
|
|
409
451
|
* before; this only reorders the empty-query case. Curated for breadth across
|
|
410
452
|
* the product's main workflows rather than raw usage frequency: help/config
|
|
411
|
-
* (orientation), panel/model (workspace + provider), recall/codebase/search
|
|
453
|
+
* (orientation), panel (legacy view aliases)/model (workspace + provider), recall/codebase/search
|
|
412
454
|
* (knowledge), workstream/checkpoint (control-plane), imagine (generation),
|
|
413
455
|
* sessions (continuity), quit (exit).
|
|
414
456
|
*/
|