@pellux/goodvibes-tui 0.27.0 → 0.29.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 +124 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,130 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.29.0] — 2026-07-03
|
|
8
|
+
|
|
9
|
+
### Changes
|
|
10
|
+
- 2842a791 perf(ci): parallelize the per-file test runner; raise the test job timeout
|
|
11
|
+
- 218bb217 merge: panel-system remediation — 38 work orders, consoles not signposts (0.29.0)
|
|
12
|
+
- a37c028e fix(input): WO-160 smoke findings — overlay write-back clobber, /ops view parity, panel-list honesty, modal viewport
|
|
13
|
+
- 5741f633 fix(tests): isolate GitPanel non-repo test from the runner's in-tree TMPDIR
|
|
14
|
+
- 6eaea7c4 fix(panels): WO-160 consistency sweep — alias tests, action-substitute signposts
|
|
15
|
+
- 42164e34 fix(panels): cockpit c/i leave the key unconsumed on terminal or absent roster entries
|
|
16
|
+
- 10247db1 feat(input): make Ctrl+O a real keybind for the Ops Control panel
|
|
17
|
+
- ac598536 refactor(panels): route selected-row reads through getSelectedItem and ban raw [this.selectedIndex]
|
|
18
|
+
- 34bac3f0 chore(panels): tighten hex-literal baseline to exact actual counts
|
|
19
|
+
- 3d2caf90 integrate WO-153 into panel-ux-remediation
|
|
20
|
+
- 1f0ed5ca integrate WO-152 into panel-ux-remediation
|
|
21
|
+
- 8b6fbce6 integrate WO-151 into panel-ux-remediation
|
|
22
|
+
- ce0b7cb8 fix(panels): closure-audit findings — incident/settings-sync filtered desyncs, subscription start signposts
|
|
23
|
+
- ba7db35e feat(panels): WO-152 registry taxonomy, icons, always-register, lifecycle flags
|
|
24
|
+
- 7cbdd4ec feat(panels): WO-153 filter-model convergence onto ScrollableListPanel
|
|
25
|
+
- 40cb9153 feat(panels): WO-151 keybindings, help discoverability, one hint grammar
|
|
26
|
+
- 782e7752 fix(panels): services test/detail act on the filtered selection
|
|
27
|
+
- e8783a89 integrate WO-154 into panel-ux-remediation
|
|
28
|
+
- cdb399b9 integrate WO-140 into panel-ux-remediation
|
|
29
|
+
- 943089ea integrate WO-139 into panel-ux-remediation
|
|
30
|
+
- d527c223 integrate WO-141 into panel-ux-remediation
|
|
31
|
+
- a65ee307 integrate WO-150 into panel-ux-remediation
|
|
32
|
+
- a73e874b feat(panels): WO-140 planning cluster polish (wrfc/project-planning/work-plan)
|
|
33
|
+
- e1921ab8 feat(panels): WO-141 keep-panel hardening + residual signpost sweep
|
|
34
|
+
- 8b018746 feat(panels): WO-139 provider-cost leftovers — subscription/local-auth/cost-tracker
|
|
35
|
+
- da7b9550 feat(panels): WO-150 unify focus ownership in PanelManager
|
|
36
|
+
- 4c569ae8 feat(shell): WO-154 main-UX shell splash, footer cost/compact, overlay posture
|
|
37
|
+
- 7218349c fix(panels): wave 3a review findings — filtered-selection desyncs, residual signposts, dead constants
|
|
38
|
+
- 1cc9f195 integrate WO-138 into panel-ux-remediation
|
|
39
|
+
- 179a9a91 integrate WO-137 into panel-ux-remediation
|
|
40
|
+
- 4df3036e integrate WO-136 into panel-ux-remediation
|
|
41
|
+
- 20533671 integrate WO-135 into panel-ux-remediation
|
|
42
|
+
- 2f116f82 integrate WO-134 into panel-ux-remediation
|
|
43
|
+
- 595df267 integrate WO-133 into panel-ux-remediation
|
|
44
|
+
- 490dc881 feat(panels): WO-137 diagnostics trio — security, debug, system-messages
|
|
45
|
+
- 066fd91a feat(panels): WO-138 session-comm quad real actions (remote/communication/routes/qr)
|
|
46
|
+
- a9a1ebe0 feat(panels): WO-134 Plugins + Hooks control rooms
|
|
47
|
+
- e4380896 feat(panels): WO-136 Docs + Intelligence live actions
|
|
48
|
+
- 8f6a3f45 feat(panels): WO-133 explorer + preview lazy loading, git decorations, diff/reload keys
|
|
49
|
+
- 744dc460 feat(panels): WO-135 marketplace + skills real actions
|
|
50
|
+
- a04c86e9 fix(panels): reserve summary row inside stacked scrollable section budgets
|
|
51
|
+
- b2a43d9b fix(panels): reserve a row for the window-summary inside the scrollable budget
|
|
52
|
+
- acb3e3b5 fix(panels): bind w to dispatch teamwork review from Tasks; stop git panel swallowing i on loaded repos
|
|
53
|
+
- 4bc04330 integrate WO-130 into panel-ux-remediation
|
|
54
|
+
- e15c010b integrate WO-128 into panel-ux-remediation
|
|
55
|
+
- 3020d2e1 integrate WO-127 into panel-ux-remediation
|
|
56
|
+
- 1a0c0d76 integrate WO-132 into panel-ux-remediation
|
|
57
|
+
- 79ac40bd integrate WO-131 into panel-ux-remediation
|
|
58
|
+
- 61092aa0 integrate WO-126 into panel-ux-remediation
|
|
59
|
+
- f83d6820 feat(panels): WO-131 agent-runtime trio — tools + tasks + orchestration
|
|
60
|
+
- 8dafc5c0 feat(panels): WO-132 Git + Diff consoles — stage/unstage/commit, explicit init confirm, self-loading diff panel
|
|
61
|
+
- 24c5127e feat(panels): WO-128 plan dashboard revival
|
|
62
|
+
- 2c8e8d1f feat(panels): WO-126 symbol outline on tree-sitter
|
|
63
|
+
- 2d424e1c feat(panels): WO-130 Cockpit operator polish
|
|
64
|
+
- 66c0deeb feat(panels): WO-127 eval console — baseline seed, live run, regressions
|
|
65
|
+
- 1a53ef2c fix(panels): drop redundant slash-command guidance from knowledge review mode
|
|
66
|
+
- 16ba104d fix(tests): operator-surfaces gate verifies forensics alias resolves to incident
|
|
67
|
+
- a5b8f4e9 integrate FOLLOWUPS into panel-ux-remediation
|
|
68
|
+
- f60988eb integrate WO-125 into panel-ux-remediation
|
|
69
|
+
- 39996bfd integrate WO-124 into panel-ux-remediation
|
|
70
|
+
- 78fb4916 integrate WO-123 into panel-ux-remediation
|
|
71
|
+
- e5294103 integrate WO-122 into panel-ux-remediation
|
|
72
|
+
- 371f4d60 integrate WO-121 into panel-ux-remediation
|
|
73
|
+
- 1ff1e0ae integrate WO-120 into panel-ux-remediation
|
|
74
|
+
- 933b09a3 feat(panels): WO-120 register ops-control panel and wire operator actions
|
|
75
|
+
- 39bfc1a1 feat(panels): WO-125 wire worktree and sandbox lifecycle verbs to their registries
|
|
76
|
+
- 4992fd03 feat(panels): WO-121 control-plane approvals console
|
|
77
|
+
- ea1c47b0 feat(panels): WO-123 knowledge console replaces static command catalogue
|
|
78
|
+
- 91273a60 feat(panels): WO-124 settings-sync console — resolve conflicts in-panel, Tab browse modes
|
|
79
|
+
- 74f5d39d feat(panels): WO-122 policy governance console actions and depth
|
|
80
|
+
- f4679a7e fix(panels): broaden hex-literal ratchet to 3/6/8-digit forms, clear remaining raw hex in fullscreen-primitives and diff-panel
|
|
81
|
+
- 6e1b814c fix(panels): forensics resolves as alias to incident, not duplicate registration
|
|
82
|
+
- 6a0f4405 fix(tests): operator-surfaces gate asserts merged provider-health id instead of retired providers id
|
|
83
|
+
- 09d81371 fix(tests): retarget legacy-turn-bus ban from deleted context-visualizer-panel to its WO-113 successor token-budget-panel
|
|
84
|
+
- e835a821 integrate WO-114 into panel-ux-remediation
|
|
85
|
+
- 433b4c25 integrate WO-113 into panel-ux-remediation
|
|
86
|
+
- 70466337 integrate WO-112 into panel-ux-remediation
|
|
87
|
+
- a1ebf5da fix(tests): contract module count 30 after WO-110+WO-111 both deleted a module
|
|
88
|
+
- fd25fb19 integrate WO-111 into panel-ux-remediation
|
|
89
|
+
- 0d72a2ef integrate WO-110 into panel-ux-remediation
|
|
90
|
+
- 0a344b60 WO-112: Provider console merge: providers + accounts into provider-health
|
|
91
|
+
- d4c7c8b4 WO-111: Automation console merge: schedule + watchers into automation
|
|
92
|
+
- 1fc22d27 WO-110: Agent console merge: agent-logs into inspector
|
|
93
|
+
- 0d4858ff WO-114: Incident console merge: forensics into incident
|
|
94
|
+
- a4cdfb3c WO-113: Context/token merge: context into tokens
|
|
95
|
+
- fd8e84ef integrate WO-002 into panel-ux-remediation
|
|
96
|
+
- 1a85ece3 WO-002: Panel palette and chrome sweep (extendPalette everywhere)
|
|
97
|
+
- 1cd556da integrate WO-006 into panel-ux-remediation
|
|
98
|
+
- d6effd51 integrate WO-005 into panel-ux-remediation
|
|
99
|
+
- d7735d20 integrate WO-004 into panel-ux-remediation
|
|
100
|
+
- 9b1de69a integrate WO-003 into panel-ux-remediation
|
|
101
|
+
- e149323f integrate WO-001 into panel-ux-remediation
|
|
102
|
+
- 45e90fa7 WO-006: Shared-test decongestion: per-panel spec modules
|
|
103
|
+
- 2d7d5777 WO-001: Theme-token backbone + architecture-gate hex enforcement
|
|
104
|
+
- 207cd46b WO-003: Action-callback plumbing pattern + ConfirmState generalization
|
|
105
|
+
- abf6771b WO-005: session-maintenance evaluator dedup
|
|
106
|
+
- 4fe16129 WO-004: Dead code and dead keybinding removal
|
|
107
|
+
|
|
108
|
+
## [0.28.0] — 2026-06-30
|
|
109
|
+
|
|
110
|
+
Panel navigation & UI/UX overhaul, plus the accumulated post-0.27.0 UX fixes.
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
- **Unified panel navigation**: a single consolidated tab bar with a focus-accent border; jump directly to any panel with `Alt+1`–`Alt+9`, click tabs, and toggle the active pane with `Ctrl+G`.
|
|
114
|
+
- **In-panel filtering**: press `/` to filter the list in panels that support it.
|
|
115
|
+
- **Mouse-wheel scrolling** on every panel (via `BasePanel.handleScroll`).
|
|
116
|
+
|
|
117
|
+
### Changed
|
|
118
|
+
- **Width-aware formatting** across panels: shared toolkit primitives (`fitDisplay`, table/tree/badge/meter helpers) replace hand-rolled `.slice()`/`.padEnd()`, so rows stay aligned across emoji, CJK, and other wide characters.
|
|
119
|
+
- **Scroll-back**: you can scroll up during and after a turn; the view only auto-follows the tail when parked at the bottom.
|
|
120
|
+
- **Auto-compaction is now owned entirely by the SDK** (`@pellux/goodvibes-sdk` 0.35.0 `handlePostTurnContextMaintenance`). The redundant TUI-side trigger was removed to prevent double-compaction; manual `/compact` now builds a richer handoff context (running agents, WRFC chains, active plan, session lineage).
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
- Timer/async panels repaint while the main thread is idle instead of looking frozen (repaints gated on panel-active state).
|
|
124
|
+
- Input/keybinding papercuts: reverse-search accept, Home/End, and the command palette.
|
|
125
|
+
- Crash/signal safety net: the terminal is always restored on uncaught exceptions and termination signals.
|
|
126
|
+
- Consolidated number/duration/context-usage formatting and routed the context window through one corrected `getContextWindowForModel` source across every surface.
|
|
127
|
+
|
|
128
|
+
### Internal
|
|
129
|
+
- Split `polish.ts` into leaf `polish-core.ts` + `polish-tables.ts` and extracted `wrfc-panel-format.ts` to satisfy the 800-line architecture gate; no public API changes (all symbols re-exported).
|
|
130
|
+
|
|
7
131
|
## [0.27.0] — 2026-06-30
|
|
8
132
|
|
|
9
133
|
Full deep-review audit of the TUI (33 findings fixed, each reviewed to a score of 10) and adoption of `@pellux/goodvibes-sdk` 0.35.0.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
6
|
|
|
7
7
|
A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared context-window usage computation.
|
|
3
|
+
*
|
|
4
|
+
* Centralises the tokens/contextWindow ratio so all panels, auto-compact,
|
|
5
|
+
* and session-maintenance read from one formula rather than six diverging
|
|
6
|
+
* hand-rolled copies.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: compaction-preview.ts intentionally displays usage > 100%
|
|
9
|
+
* ("exceeds window") — callers that need the unclamped value must use
|
|
10
|
+
* rawRatio, not clampedRatio / pct.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface ContextUsage {
|
|
14
|
+
/**
|
|
15
|
+
* Raw (unclamped) ratio: max(0, tokens) / contextWindow.
|
|
16
|
+
* May exceed 1.0 when token count surpasses the context window.
|
|
17
|
+
* 0 when contextWindow <= 0.
|
|
18
|
+
*/
|
|
19
|
+
rawRatio: number;
|
|
20
|
+
/**
|
|
21
|
+
* Ratio clamped to [0, 1]. Use for progress-bar fill and color bands.
|
|
22
|
+
*/
|
|
23
|
+
clampedRatio: number;
|
|
24
|
+
/**
|
|
25
|
+
* Integer percentage clamped to [0, 100].
|
|
26
|
+
* Equivalent to Math.min(100, Math.round(rawRatio * 100)).
|
|
27
|
+
* Use for numeric displays and threshold comparisons.
|
|
28
|
+
*/
|
|
29
|
+
pct: number;
|
|
30
|
+
/**
|
|
31
|
+
* Remaining tokens: max(0, contextWindow - tokens).
|
|
32
|
+
* 0 when contextWindow <= 0.
|
|
33
|
+
*/
|
|
34
|
+
remaining: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compute context-window usage metrics from raw token counts.
|
|
39
|
+
*
|
|
40
|
+
* @param tokens Current input-token count (negative values are treated as 0).
|
|
41
|
+
* @param contextWindow Model context window size (0 or negative → all fields return 0).
|
|
42
|
+
*/
|
|
43
|
+
export function computeContextUsage(tokens: number, contextWindow: number): ContextUsage {
|
|
44
|
+
if (contextWindow <= 0) {
|
|
45
|
+
return { rawRatio: 0, clampedRatio: 0, pct: 0, remaining: 0 };
|
|
46
|
+
}
|
|
47
|
+
const safeTokens = Math.max(0, tokens);
|
|
48
|
+
const rawRatio = safeTokens / contextWindow;
|
|
49
|
+
const clampedRatio = Math.min(1, rawRatio);
|
|
50
|
+
const pct = Math.min(100, Math.round(rawRatio * 100));
|
|
51
|
+
const remaining = Math.max(0, contextWindow - tokens);
|
|
52
|
+
return { rawRatio, clampedRatio, pct, remaining };
|
|
53
|
+
}
|
package/src/core/conversation.ts
CHANGED
|
@@ -110,7 +110,7 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
110
110
|
* Message index at the time of the last clearDisplay() call.
|
|
111
111
|
* rebuildHistory() renders only messages at or after this index, so the
|
|
112
112
|
* display stays blank for messages added before the clear while LLM history
|
|
113
|
-
* is fully preserved.
|
|
113
|
+
* is fully preserved. Persists until resetAll(). rebuildHistory() never resets this value.
|
|
114
114
|
*/
|
|
115
115
|
private _displayFromMessageIndex = 0;
|
|
116
116
|
|
|
@@ -404,8 +404,6 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
404
404
|
|
|
405
405
|
// When _displayFromMessageIndex > 0, clearDisplay() was called. Only render
|
|
406
406
|
// messages added after the clear — the pre-clear history stays off-screen.
|
|
407
|
-
// On a full rebuild (e.g. width change), reset the display-start to 0 so the
|
|
408
|
-
// user can scroll back to the full history if needed.
|
|
409
407
|
const displayStart = this._displayFromMessageIndex;
|
|
410
408
|
const visibleSnapshot = displayStart > 0 ? renderSnapshot.slice(displayStart) : renderSnapshot;
|
|
411
409
|
|
|
@@ -662,9 +660,9 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
662
660
|
// messageKindRegistry is NOT cleared here. The underlying messages array is
|
|
663
661
|
// preserved by clearDisplay(); kind entries for pre-clear messages are harmless
|
|
664
662
|
// because those messages are hidden by _displayFromMessageIndex and never rendered.
|
|
665
|
-
// Clearing the registry would cause kind loss for pre-clear messages
|
|
666
|
-
//
|
|
667
|
-
//
|
|
663
|
+
// Clearing the registry would cause kind loss for pre-clear messages; if
|
|
664
|
+
// resetAll() later restores displayStart to 0, those messages re-render
|
|
665
|
+
// and their kind entries must still be present to suppress navigation.
|
|
668
666
|
// Advance _displayFromMessageIndex to exclude all current messages from display.
|
|
669
667
|
// rebuildHistory() will only render messages added AFTER this point.
|
|
670
668
|
this._displayFromMessageIndex = this.getMessageSnapshot().length;
|
|
@@ -115,6 +115,8 @@ export function replayJournalIntoConversation(
|
|
|
115
115
|
conversation.fromJSON({
|
|
116
116
|
...preserved,
|
|
117
117
|
messages: replayedMessages as never[],
|
|
118
|
+
title: conversation.title,
|
|
119
|
+
titleSource: conversation.getTitleSource(),
|
|
118
120
|
});
|
|
119
121
|
conversation.rebuildHistory();
|
|
120
122
|
|
|
@@ -19,6 +19,7 @@ interface TurnOrchestrator {
|
|
|
19
19
|
/** Minimal provider registry surface required by turn-event wiring. */
|
|
20
20
|
interface TurnProviderRegistry {
|
|
21
21
|
getCurrentModel(): { readonly contextWindow: number };
|
|
22
|
+
getContextWindowForModel(model: { readonly contextWindow: number }): number;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/** Minimal config manager surface required by turn-event wiring. */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import { CostTrackerPanel } from '../../panels/cost-tracker-panel.ts';
|
|
3
|
+
import { openCommandPanel, requirePanelManager } from './runtime-services.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* /cost budget <usd> — makes the CostTrackerPanel's budget alert real (WO-139).
|
|
7
|
+
* Opens the cost panel (creating it via the registered factory if not already
|
|
8
|
+
* open) and sets its budget threshold directly, so the meter+alert already
|
|
9
|
+
* built into CostTrackerPanel.render() (:266-290) actually fires. 0 disables
|
|
10
|
+
* the alert. Mirrors the /auth local rotate-password → panel-instance-call
|
|
11
|
+
* pattern (local-auth-runtime.ts) rather than only printing a signpost.
|
|
12
|
+
*/
|
|
13
|
+
export function registerCostRuntimeCommands(registry: CommandRegistry): void {
|
|
14
|
+
registry.register({
|
|
15
|
+
name: 'cost',
|
|
16
|
+
description: 'Inspect session/agent cost tracking and configure the budget alert threshold',
|
|
17
|
+
usage: '[panel|budget <usd>]',
|
|
18
|
+
handler(args, ctx) {
|
|
19
|
+
const sub = (args[0] ?? 'panel').toLowerCase();
|
|
20
|
+
|
|
21
|
+
if (sub === 'panel' || sub === 'open') {
|
|
22
|
+
openCommandPanel(ctx, 'cost');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (sub === 'budget') {
|
|
27
|
+
const raw = args[1];
|
|
28
|
+
const usd = raw !== undefined ? Number(raw) : NaN;
|
|
29
|
+
if (raw === undefined || !Number.isFinite(usd) || usd < 0) {
|
|
30
|
+
ctx.print('Usage: /cost budget <usd> (0 disables the alert)');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const panelManager = requirePanelManager(ctx);
|
|
34
|
+
const panel = panelManager.open('cost');
|
|
35
|
+
if (panel instanceof CostTrackerPanel) {
|
|
36
|
+
panel.setBudgetThreshold(usd);
|
|
37
|
+
ctx.print(usd > 0
|
|
38
|
+
? `Cost budget alert set to $${usd.toFixed(2)}.`
|
|
39
|
+
: 'Cost budget alert disabled.');
|
|
40
|
+
} else {
|
|
41
|
+
ctx.print('Cost tracking is not available in this session.');
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
ctx.print('Usage: /cost [panel|budget <usd>]');
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -109,8 +109,9 @@ async function handleCompare(args: string[], context: CommandContext): Promise<v
|
|
|
109
109
|
// ── /eval gate ────────────────────────────────────────────────────────────────
|
|
110
110
|
|
|
111
111
|
async function handleGate(args: string[], context: CommandContext): Promise<void> {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
112
|
+
const positionals = args.filter(a => !a.startsWith('--'));
|
|
113
|
+
const suiteName = positionals[0];
|
|
114
|
+
const baselineFile = positionals[1] ?? '.goodvibes/eval/baseline.json';
|
|
114
115
|
const saveFlag = args.includes('--save-baseline');
|
|
115
116
|
const projectRoot = requireShellPaths(context).workingDirectory;
|
|
116
117
|
|
|
@@ -141,7 +142,7 @@ async function handleGate(args: string[], context: CommandContext): Promise<void
|
|
|
141
142
|
context.print(formatGateResult(gate));
|
|
142
143
|
|
|
143
144
|
if (saveFlag || !baseline) {
|
|
144
|
-
const label =
|
|
145
|
+
const label = positionals[0] ?? 'latest';
|
|
145
146
|
const newBaseline = captureBaseline(label, [fresh]);
|
|
146
147
|
try {
|
|
147
148
|
await writeBaseline(baselineFile, newBaseline, projectRoot);
|
|
@@ -175,8 +175,12 @@ export function registerOperatorRuntimeCommands(registry: CommandRegistry): void
|
|
|
175
175
|
const sub = args[0];
|
|
176
176
|
|
|
177
177
|
if (sub === 'view' || sub === undefined) {
|
|
178
|
+
// Parity with the Ctrl+O keybind (handler-shortcuts.ts): prefer the
|
|
179
|
+
// flag-wired opener, else fall back to the always-registered panel,
|
|
180
|
+
// which renders its own honest not-configured state.
|
|
178
181
|
if (ctx.openOpsPanel) ctx.openOpsPanel();
|
|
179
|
-
else ctx.
|
|
182
|
+
else if (ctx.showPanel) ctx.showPanel('ops-control');
|
|
183
|
+
else ctx.print('Operator Control Plane panel is not available in this shell.');
|
|
180
184
|
return;
|
|
181
185
|
}
|
|
182
186
|
|
package/src/input/commands.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime
|
|
|
56
56
|
import { registerTtsRuntimeCommands } from './commands/tts-runtime.ts';
|
|
57
57
|
import { registerCloudflareRuntimeCommands } from './commands/cloudflare-runtime.ts';
|
|
58
58
|
import { registerWorkPlanRuntimeCommands } from './commands/work-plan-runtime.ts';
|
|
59
|
+
import { registerCostRuntimeCommands } from './commands/cost-runtime.ts';
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
62
|
* registerBuiltinCommands - Register all built-in slash commands into the registry.
|
|
@@ -107,6 +108,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
107
108
|
registerTtsRuntimeCommands(registry);
|
|
108
109
|
registerCloudflareRuntimeCommands(registry);
|
|
109
110
|
registerWorkPlanRuntimeCommands(registry);
|
|
111
|
+
registerCostRuntimeCommands(registry);
|
|
110
112
|
registerLocalRuntimeCommands(registry);
|
|
111
113
|
registerDiscoveryRuntimeCommands(registry);
|
|
112
114
|
registerPlanningRuntimeCommands(registry);
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
} from './handler-prompt-buffer.ts';
|
|
14
14
|
import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, registerPaste } from './handler-content-actions.ts';
|
|
15
15
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
16
|
+
import { renderPanelWorkspaceBar } from '../renderer/panel-workspace-bar.ts';
|
|
17
|
+
import type { TabHitRegion } from '../renderer/tab-strip.ts';
|
|
16
18
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
17
19
|
import type { KillRing } from './kill-ring.ts';
|
|
18
20
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
@@ -76,31 +78,25 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
76
78
|
return { handled: true, panelFocused };
|
|
77
79
|
}
|
|
78
80
|
const kb = state.keybindingsManager;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (kb.matches('panel-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
state.requestRender();
|
|
92
|
-
return { handled: true, panelFocused };
|
|
93
|
-
}
|
|
94
|
-
if (kb.matches('panel-close', token)) {
|
|
95
|
-
const pm = state.panelManager;
|
|
96
|
-
const active = pm.getActivePanel();
|
|
97
|
-
if (active) {
|
|
98
|
-
pm.close(active.id);
|
|
99
|
-
}
|
|
100
|
-
panelFocused = false;
|
|
81
|
+
// NOTE: panel-tab-next/prev, panel-close, and panel-close-all are handled
|
|
82
|
+
// globally in handleGlobalShortcutToken, which runs earlier in the feed
|
|
83
|
+
// loop and consumes those tokens before they ever reach this focused-panel
|
|
84
|
+
// route. Their old copies lived here too and had drifted (this route never
|
|
85
|
+
// called pm.hide() on close-all); they were unreachable, so they are gone
|
|
86
|
+
// and close/close-all semantics now live in exactly one place. Only
|
|
87
|
+
// panel-focus-toggle stays here, because it is meaningful only while the
|
|
88
|
+
// panel workspace already owns focus (it swaps between the two panes).
|
|
89
|
+
if (kb.matches('panel-focus-toggle', token)) {
|
|
90
|
+
// Switch keyboard focus between the top and bottom panes (no-op when
|
|
91
|
+
// there is no visible, non-empty bottom pane).
|
|
92
|
+
state.panelManager.togglePaneFocus();
|
|
101
93
|
state.requestRender();
|
|
102
94
|
return { handled: true, panelFocused };
|
|
103
95
|
}
|
|
96
|
+
// Alt+1..9 (panel-tab-1..9) is a global KeyAction consumed earlier by
|
|
97
|
+
// handleGlobalShortcutToken, so those meta+digit tokens never reach here.
|
|
98
|
+
// Any other ctrl/meta combo this route does not own falls through so the
|
|
99
|
+
// global handler (which ran first) or the prompt routes can act on it.
|
|
104
100
|
if (token.ctrl || token.meta) {
|
|
105
101
|
return { handled: false, panelFocused };
|
|
106
102
|
}
|
|
@@ -577,18 +573,48 @@ function getPanelUnderMouse(
|
|
|
577
573
|
return getActivePanelInPane(panelManager, 'top');
|
|
578
574
|
}
|
|
579
575
|
|
|
576
|
+
// Single consolidated workspace bar (row 0) + h-separator; the rest splits
|
|
577
|
+
// between the two panes' content.
|
|
580
578
|
const panelAreaRows = Math.max(0, layout.height - 1);
|
|
581
|
-
const contentRows = Math.max(0, panelAreaRows -
|
|
579
|
+
const contentRows = Math.max(0, panelAreaRows - 1);
|
|
582
580
|
const topContentRows = contentRows <= 1
|
|
583
581
|
? contentRows
|
|
584
582
|
: Math.max(1, Math.floor(contentRows * clampRatio(layout.verticalSplitRatio)));
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
return panelRow <= topLastRow
|
|
583
|
+
// panelRow 0 = workspace bar; rows 1..topContentRows = top pane; rest = bottom.
|
|
584
|
+
return panelRow <= topContentRows
|
|
588
585
|
? getActivePanelInPane(panelManager, 'top')
|
|
589
586
|
: getActivePanelInPane(panelManager, 'bottom');
|
|
590
587
|
}
|
|
591
588
|
|
|
589
|
+
/**
|
|
590
|
+
* If the mouse is over the consolidated workspace tab bar (the first panel
|
|
591
|
+
* row), return the index of the tab under the cursor, else null. Recomputes the
|
|
592
|
+
* tab hit regions by rendering the bar with a layout callback — cheap and keeps
|
|
593
|
+
* the click geometry in lockstep with what was drawn.
|
|
594
|
+
*/
|
|
595
|
+
function workspaceTabAtMouse(
|
|
596
|
+
panelManager: PanelManager,
|
|
597
|
+
layout: PanelMouseLayout | null,
|
|
598
|
+
row: number,
|
|
599
|
+
col: number,
|
|
600
|
+
): number | null {
|
|
601
|
+
if (
|
|
602
|
+
layout === null
|
|
603
|
+
|| !panelManager.isVisible()
|
|
604
|
+
|| panelManager.getAllOpen().length === 0
|
|
605
|
+
|| row !== layout.y // workspace bar is the first panel row
|
|
606
|
+
|| col < layout.x
|
|
607
|
+
|| col >= layout.x + layout.width
|
|
608
|
+
) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
let regions: readonly TabHitRegion[] = [];
|
|
612
|
+
renderPanelWorkspaceBar(panelManager.getWorkspaceTabs(), layout.width, true, (r) => { regions = r; });
|
|
613
|
+
const relCol = col - layout.x;
|
|
614
|
+
const hit = regions.find((rg) => relCol >= rg.startCol && relCol < rg.endCol);
|
|
615
|
+
return hit ? hit.index : null;
|
|
616
|
+
}
|
|
617
|
+
|
|
592
618
|
function scrollPanelUnderMouse(
|
|
593
619
|
state: MouseRouteState,
|
|
594
620
|
token: Extract<InputToken, { type: 'mouse' }>,
|
|
@@ -634,6 +660,14 @@ export function handleMouseToken(state: MouseRouteState, token: InputToken): {
|
|
|
634
660
|
return { handled: true, mouseDownRow, mouseDownCol };
|
|
635
661
|
}
|
|
636
662
|
if (token.button === 0 && token.action === 'press') {
|
|
663
|
+
// Click on a workspace tab activates it (checked before starting a text
|
|
664
|
+
// selection so a tab click doesn't begin a drag-select).
|
|
665
|
+
const tabIndex = workspaceTabAtMouse(state.panelManager, state.panelMouseLayout, token.row, token.col);
|
|
666
|
+
if (tabIndex !== null) {
|
|
667
|
+
state.panelManager.activateWorkspaceIndex(tabIndex);
|
|
668
|
+
state.requestRender();
|
|
669
|
+
return { handled: true, mouseDownRow, mouseDownCol };
|
|
670
|
+
}
|
|
637
671
|
mouseDownRow = token.row;
|
|
638
672
|
mouseDownCol = token.col;
|
|
639
673
|
state.selection.startSelection(token.col, viewportRow, state.scrollTop, state.viewportHeight, state.lineCount);
|
|
@@ -251,14 +251,10 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
if (token.type === 'key') {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|| context.panelManager.getActivePanel() === null)
|
|
259
|
-
) {
|
|
260
|
-
context.panelFocused = false;
|
|
261
|
-
}
|
|
254
|
+
// Focus can never disagree with workspace visibility: PanelManager owns
|
|
255
|
+
// focusTarget and self-heals it, and context.panelFocused was seeded from
|
|
256
|
+
// it at feed entry — so no manual "unfocus if panels vanished" patch is
|
|
257
|
+
// needed here anymore.
|
|
262
258
|
const shortcutState = {
|
|
263
259
|
panelFocused: context.panelFocused,
|
|
264
260
|
prompt: context.prompt,
|
|
@@ -296,6 +292,19 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
296
292
|
context.cursorPos = shortcutState.cursorPos;
|
|
297
293
|
context.commandMode = shortcutState.commandMode;
|
|
298
294
|
context.panelFocused = shortcutState.panelFocused;
|
|
295
|
+
if (context.commandMode) {
|
|
296
|
+
if (!context.prompt.startsWith('/')) {
|
|
297
|
+
context.commandMode = false;
|
|
298
|
+
context.autocomplete?.reset();
|
|
299
|
+
} else {
|
|
300
|
+
const q = context.prompt.slice(1);
|
|
301
|
+
if (q.indexOf(' ') === -1) {
|
|
302
|
+
context.autocomplete?.update(q);
|
|
303
|
+
} else {
|
|
304
|
+
context.autocomplete?.reset();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
299
308
|
continue;
|
|
300
309
|
}
|
|
301
310
|
}
|
|
@@ -98,13 +98,22 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
98
98
|
if (capModel) {
|
|
99
99
|
const rawInput = state.modelPicker.contextCapQuery.trim();
|
|
100
100
|
const parsedCap = rawInput.length > 0 ? parseInt(rawInput, 10) : null;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
if (parsedCap !== null && (parsedCap <= 0 || parsedCap > 10_000_000)) {
|
|
102
|
+
// Non-empty but out of valid range — surface notice; keep picker open for correction.
|
|
103
|
+
state.modelPicker.contextCapError = 'Context cap must be 1–10,000,000';
|
|
104
|
+
} else {
|
|
105
|
+
state.modelPicker.contextCapError = null;
|
|
106
|
+
const validCap = parsedCap !== null && parsedCap > 0 && parsedCap <= 10_000_000 ? parsedCap : null;
|
|
107
|
+
const effort = state.commandContext?.session.runtime.reasoningEffort ?? 'medium';
|
|
108
|
+
const handled = state.onModelPickerCommit?.() ?? false;
|
|
109
|
+
if (!handled) state.commandContext?.completeModelSelection?.({ model: capModel, effort, contextCap: validCap, target: state.modelPicker.target });
|
|
110
|
+
state.modelPicker.close();
|
|
111
|
+
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
state.modelPicker.close();
|
|
115
|
+
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
105
116
|
}
|
|
106
|
-
state.modelPicker.close();
|
|
107
|
-
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
108
117
|
}
|
|
109
118
|
} else if (token.logicalName === 'up') {
|
|
110
119
|
if (state.modelPicker.focusPane === 'targets') {
|
|
@@ -158,14 +167,10 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
158
167
|
if (state.modelPicker.mode === 'contextCap') {
|
|
159
168
|
if (token.value.length === 1) state.modelPicker.appendContextCapChar(token.value);
|
|
160
169
|
} else if ((state.modelPicker.mode === 'model' || state.modelPicker.mode === 'provider') && state.modelPicker.searchFocused) {
|
|
170
|
+
// When search is focused every printable char — including space — goes to the query.
|
|
171
|
+
// The space=context-cap shortcut remains active only in the non-search branch below.
|
|
161
172
|
const ch = token.value;
|
|
162
|
-
if (ch === ' '
|
|
163
|
-
const selected = state.modelPicker.getSelected();
|
|
164
|
-
if (selected && state.modelPicker.isLocalModel(selected)) state.modelPicker.enterContextCapMode(selected);
|
|
165
|
-
else if (ch.length === 1 && ch >= ' ') state.modelPicker.appendChar(ch);
|
|
166
|
-
} else if (ch.length === 1 && ch >= ' ') {
|
|
167
|
-
state.modelPicker.appendChar(ch);
|
|
168
|
-
}
|
|
173
|
+
if (ch.length === 1 && ch >= ' ') state.modelPicker.appendChar(ch);
|
|
169
174
|
} else if (token.value === ' ' && state.modelPicker.mode === 'model') {
|
|
170
175
|
const selected = state.modelPicker.getSelected();
|
|
171
176
|
if (selected && state.modelPicker.isLocalModel(selected)) state.modelPicker.enterContextCapMode(selected);
|
|
@@ -115,6 +115,21 @@ export function handleGlobalShortcutToken(
|
|
|
115
115
|
state.requestRender();
|
|
116
116
|
return true;
|
|
117
117
|
|
|
118
|
+
case 'panel-focus-toggle': {
|
|
119
|
+
// Global entry point for the focus-toggle key (Ctrl+G): from the prompt
|
|
120
|
+
// it grabs focus for the panel workspace. Once the workspace already has
|
|
121
|
+
// focus we let it fall through (return false) so handlePanelFocusToken
|
|
122
|
+
// can do the top/bottom pane swap — keeping that behavior in one place.
|
|
123
|
+
if (state.panelFocused) return false;
|
|
124
|
+
const pm = state.panelManager;
|
|
125
|
+
if (pm.isVisible() && pm.getAllOpen().length > 0) {
|
|
126
|
+
state.panelFocused = true;
|
|
127
|
+
state.requestRender();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
118
133
|
case 'panel-tab-next':
|
|
119
134
|
state.cyclePanelTab('next');
|
|
120
135
|
return true;
|
|
@@ -123,6 +138,42 @@ export function handleGlobalShortcutToken(
|
|
|
123
138
|
state.cyclePanelTab('prev');
|
|
124
139
|
return true;
|
|
125
140
|
|
|
141
|
+
case 'panel-tab-1':
|
|
142
|
+
case 'panel-tab-2':
|
|
143
|
+
case 'panel-tab-3':
|
|
144
|
+
case 'panel-tab-4':
|
|
145
|
+
case 'panel-tab-5':
|
|
146
|
+
case 'panel-tab-6':
|
|
147
|
+
case 'panel-tab-7':
|
|
148
|
+
case 'panel-tab-8':
|
|
149
|
+
case 'panel-tab-9': {
|
|
150
|
+
// Alt+1..9: jump directly to the Nth workspace tab. Routed globally (like
|
|
151
|
+
// panel-tab-next/prev) so the jump works whether focus is on the prompt or
|
|
152
|
+
// the workspace; gated on visibility, matching cyclePanelTab semantics.
|
|
153
|
+
const pm = state.panelManager;
|
|
154
|
+
if (pm.isVisible()) {
|
|
155
|
+
const index = Number(action.slice('panel-tab-'.length)) - 1;
|
|
156
|
+
pm.activateWorkspaceIndex(index);
|
|
157
|
+
state.requestRender();
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'panel-ops': {
|
|
163
|
+
// Ctrl+O: open the Ops Control panel. Prefer the command-context callback
|
|
164
|
+
// (wired only when the operator-control-plane feature flag is on) so it
|
|
165
|
+
// reuses whatever show/open semantics bootstrap.ts set up; otherwise fall
|
|
166
|
+
// back to opening the always-registered 'ops-control' panel type directly
|
|
167
|
+
// — it renders an honest not-configured state when its deps are absent.
|
|
168
|
+
if (state.commandContext?.openOpsPanel) {
|
|
169
|
+
state.commandContext.openOpsPanel();
|
|
170
|
+
} else {
|
|
171
|
+
state.panelManager.open('ops-control');
|
|
172
|
+
state.requestRender();
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
126
177
|
case 'history-search':
|
|
127
178
|
state.historySearch.open(state.prompt);
|
|
128
179
|
state.requestRender();
|
|
@@ -354,6 +354,10 @@ export function handleHistorySearchToken(state: HistorySearchRouteState, token:
|
|
|
354
354
|
state.historySearch.stepOlder();
|
|
355
355
|
} else if (token.ctrl && token.logicalName === 's') {
|
|
356
356
|
state.historySearch.stepNewer();
|
|
357
|
+
} else if (token.logicalName === 'up') {
|
|
358
|
+
state.historySearch.stepOlder();
|
|
359
|
+
} else if (token.logicalName === 'down') {
|
|
360
|
+
state.historySearch.stepNewer();
|
|
357
361
|
}
|
|
358
362
|
}
|
|
359
363
|
|