@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
|
@@ -29,6 +29,8 @@ export interface WorkspaceTab {
|
|
|
29
29
|
export class PanelManager {
|
|
30
30
|
private registry: PanelRegistration[] = [];
|
|
31
31
|
private retainedPanels = new Map<string, Panel>();
|
|
32
|
+
/** Old/absorbed panel id -> merged target id (WO-1xx console merges). */
|
|
33
|
+
private aliases = new Map<string, string>();
|
|
32
34
|
private _visible: boolean = false;
|
|
33
35
|
private _splitRatio: number = 0.6;
|
|
34
36
|
|
|
@@ -39,15 +41,48 @@ export class PanelManager {
|
|
|
39
41
|
private _verticalSplitRatio: number = 0.5; // top gets 50% of panel height
|
|
40
42
|
private _bottomPaneVisible: boolean = false;
|
|
41
43
|
|
|
44
|
+
// Single source of truth for prompt-vs-panel keyboard focus. Previously this
|
|
45
|
+
// lived as a `panelFocused` boolean scattered across InputHandler and the
|
|
46
|
+
// input-routing seams; centralizing it here is what makes it impossible for
|
|
47
|
+
// panel focus to disagree with workspace visibility (see getFocusTarget).
|
|
48
|
+
private _focusTarget: 'prompt' | 'panel' = 'prompt';
|
|
49
|
+
|
|
42
50
|
// Cache for getWorkspaceTabs() — invalidated on every panel lifecycle event
|
|
43
51
|
private _cachedWorkspaceTabs: readonly WorkspaceTab[] | null = null;
|
|
44
52
|
|
|
53
|
+
// Most-recently-opened panel ids (front = newest), for the picker's "Recent"
|
|
54
|
+
// group. Session-scoped; capped to keep it a short list.
|
|
55
|
+
private _recentlyOpened: string[] = [];
|
|
56
|
+
private static readonly RECENT_CAP = 8;
|
|
57
|
+
|
|
58
|
+
/** Record a panel id as most-recently-opened (front of the ring, deduped). */
|
|
59
|
+
private _recordRecent(panelId: string): void {
|
|
60
|
+
this._recentlyOpened = [panelId, ...this._recentlyOpened.filter((id) => id !== panelId)]
|
|
61
|
+
.slice(0, PanelManager.RECENT_CAP);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Most-recently-opened panel ids, newest first. */
|
|
65
|
+
getRecentlyOpened(): readonly string[] {
|
|
66
|
+
return this._recentlyOpened;
|
|
67
|
+
}
|
|
68
|
+
|
|
45
69
|
// -------------------------------------------------------------------------
|
|
46
70
|
// Registration
|
|
47
71
|
// -------------------------------------------------------------------------
|
|
48
72
|
|
|
49
73
|
registerType(registration: PanelRegistration): void {
|
|
50
74
|
const existing = this.registry.findIndex(r => r.id === registration.id);
|
|
75
|
+
// WO-152: registry-time icon-uniqueness assertion. Tab-bar icons are a
|
|
76
|
+
// single glyph; two panels sharing one silently made the workspace tab
|
|
77
|
+
// strip ambiguous (the historical W/R/U/K/M/Q/Y/J/P collisions). Compare
|
|
78
|
+
// against every OTHER registration (excluding a re-registration of the
|
|
79
|
+
// same id, which is a legitimate update, e.g. tests replacing a factory).
|
|
80
|
+
const iconOwner = this.registry.find(r => r.id !== registration.id && r.icon === registration.icon);
|
|
81
|
+
if (iconOwner) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Panel icon '${registration.icon}' for '${registration.id}' collides with already-registered panel '${iconOwner.id}'. Panel icons must be unique across the registry.`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
51
86
|
if (existing >= 0) {
|
|
52
87
|
this.registry[existing] = registration;
|
|
53
88
|
} else {
|
|
@@ -55,6 +90,19 @@ export class PanelManager {
|
|
|
55
90
|
}
|
|
56
91
|
}
|
|
57
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Register a compat redirect so an absorbed panel's old id still resolves
|
|
95
|
+
* after a console merge (docs, saved layouts, and muscle memory do not
|
|
96
|
+
* break). Resolved by open/close/activateById/getPanel/getPaneOf.
|
|
97
|
+
*/
|
|
98
|
+
registerAlias(aliasId: string, targetId: string): void {
|
|
99
|
+
this.aliases.set(aliasId, targetId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private _resolveId(panelId: string): string {
|
|
103
|
+
return this.aliases.get(panelId) ?? panelId;
|
|
104
|
+
}
|
|
105
|
+
|
|
58
106
|
getRegisteredTypes(): PanelRegistration[] {
|
|
59
107
|
return [...this.registry];
|
|
60
108
|
}
|
|
@@ -87,11 +135,20 @@ export class PanelManager {
|
|
|
87
135
|
this._cachedWorkspaceTabs = null;
|
|
88
136
|
}
|
|
89
137
|
|
|
90
|
-
open(
|
|
138
|
+
open(panelIdOrAlias: string, pane?: 'top' | 'bottom'): Panel {
|
|
139
|
+
const panelId = this._resolveId(panelIdOrAlias);
|
|
140
|
+
this._recordRecent(panelId);
|
|
91
141
|
const existingPane = this._findPaneOf(panelId);
|
|
92
142
|
if (existingPane) {
|
|
93
|
-
this._activateByIdInPane(panelId, existingPane);
|
|
94
143
|
this._visible = true;
|
|
144
|
+
// Honor an explicitly requested pane so open(id, pane) never lies about
|
|
145
|
+
// where the panel lands: relocate it if it currently lives in the other
|
|
146
|
+
// pane (fixes `/panel open <id> top` and the panel-list T/B move keys).
|
|
147
|
+
if (pane && pane !== existingPane) {
|
|
148
|
+
this._moveBetweenPanes(existingPane, pane, panelId);
|
|
149
|
+
return this.getPanel(panelId)!;
|
|
150
|
+
}
|
|
151
|
+
this._activateByIdInPane(panelId, existingPane);
|
|
95
152
|
this._focusedPane = existingPane;
|
|
96
153
|
if (existingPane === 'bottom') this._bottomPaneVisible = true;
|
|
97
154
|
return this._getPane(existingPane).panels[this._getPane(existingPane).activeIndex]!;
|
|
@@ -119,7 +176,8 @@ export class PanelManager {
|
|
|
119
176
|
return panel;
|
|
120
177
|
}
|
|
121
178
|
|
|
122
|
-
close(
|
|
179
|
+
close(panelIdOrAlias: string): void {
|
|
180
|
+
const panelId = this._resolveId(panelIdOrAlias);
|
|
123
181
|
// Search both panes
|
|
124
182
|
for (const which of ['top', 'bottom'] as const) {
|
|
125
183
|
const p = this._getPane(which);
|
|
@@ -231,7 +289,8 @@ export class PanelManager {
|
|
|
231
289
|
this._invalidateWorkspaceTabs();
|
|
232
290
|
}
|
|
233
291
|
|
|
234
|
-
activateById(
|
|
292
|
+
activateById(panelIdOrAlias: string): void {
|
|
293
|
+
const panelId = this._resolveId(panelIdOrAlias);
|
|
235
294
|
const which = this._findPaneOf(panelId);
|
|
236
295
|
if (!which) return;
|
|
237
296
|
this._activateByIdInPane(panelId, which);
|
|
@@ -263,6 +322,49 @@ export class PanelManager {
|
|
|
263
322
|
this._invalidateWorkspaceTabs();
|
|
264
323
|
}
|
|
265
324
|
|
|
325
|
+
// -------------------------------------------------------------------------
|
|
326
|
+
// Keyboard focus ownership (prompt vs. panel workspace)
|
|
327
|
+
// -------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Which surface owns keyboard focus. Self-healing: focus can only rest on the
|
|
331
|
+
* panel workspace while it is visible, non-empty, and has an active panel, so
|
|
332
|
+
* panel focus can never disagree with workspace visibility. Any code that
|
|
333
|
+
* asks for the focus target therefore reads a value that is always consistent
|
|
334
|
+
* with what is actually on screen.
|
|
335
|
+
*/
|
|
336
|
+
getFocusTarget(): 'prompt' | 'panel' {
|
|
337
|
+
if (this._focusTarget === 'panel' && !this._workspaceIsFocusable()) {
|
|
338
|
+
this._focusTarget = 'prompt';
|
|
339
|
+
}
|
|
340
|
+
return this._focusTarget;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** True when the panel workspace currently owns keyboard focus. */
|
|
344
|
+
isPanelFocused(): boolean {
|
|
345
|
+
return this.getFocusTarget() === 'panel';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Give keyboard focus to the panel workspace. No-op when there is nothing
|
|
350
|
+
* focusable (no visible, non-empty pane with an active panel) — this is the
|
|
351
|
+
* guard that upholds the focus/visibility invariant on the write path.
|
|
352
|
+
*/
|
|
353
|
+
focusPanels(): void {
|
|
354
|
+
if (this._workspaceIsFocusable()) {
|
|
355
|
+
this._focusTarget = 'panel';
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Return keyboard focus to the prompt. */
|
|
360
|
+
focusPrompt(): void {
|
|
361
|
+
this._focusTarget = 'prompt';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private _workspaceIsFocusable(): boolean {
|
|
365
|
+
return this._visible && this.getAllOpen().length > 0 && this.getActivePanel() !== null;
|
|
366
|
+
}
|
|
367
|
+
|
|
266
368
|
// -------------------------------------------------------------------------
|
|
267
369
|
// Pane visibility
|
|
268
370
|
// -------------------------------------------------------------------------
|
|
@@ -285,10 +387,11 @@ export class PanelManager {
|
|
|
285
387
|
this.bottomPane.panels.push(panel);
|
|
286
388
|
this.bottomPane.activeIndex = 0;
|
|
287
389
|
} else {
|
|
288
|
-
// Open a default panel in bottom pane
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
390
|
+
// Open a predictable default panel in the bottom pane (the panel list),
|
|
391
|
+
// rather than an arbitrary registration-order-dependent panel.
|
|
392
|
+
const defaultPanel = this._getRegistration('panel-list') ?? this.registry[0];
|
|
393
|
+
if (defaultPanel) {
|
|
394
|
+
this.open(defaultPanel.id, 'bottom');
|
|
292
395
|
}
|
|
293
396
|
}
|
|
294
397
|
}
|
|
@@ -335,14 +438,15 @@ export class PanelManager {
|
|
|
335
438
|
return p.panels[p.activeIndex] ?? null;
|
|
336
439
|
}
|
|
337
440
|
|
|
338
|
-
getPanel(
|
|
441
|
+
getPanel(panelIdOrAlias: string): Panel | null {
|
|
442
|
+
const panelId = this._resolveId(panelIdOrAlias);
|
|
339
443
|
return this.topPane.panels.find((panel) => panel.id === panelId)
|
|
340
444
|
?? this.bottomPane.panels.find((panel) => panel.id === panelId)
|
|
341
445
|
?? null;
|
|
342
446
|
}
|
|
343
447
|
|
|
344
|
-
getPaneOf(
|
|
345
|
-
return this._findPaneOf(
|
|
448
|
+
getPaneOf(panelIdOrAlias: string): 'top' | 'bottom' | null {
|
|
449
|
+
return this._findPaneOf(this._resolveId(panelIdOrAlias));
|
|
346
450
|
}
|
|
347
451
|
|
|
348
452
|
getWorkspaceTabs(): readonly WorkspaceTab[] {
|
|
@@ -460,7 +564,9 @@ export class PanelManager {
|
|
|
460
564
|
this.bottomPane = { panels: [], activeIndex: 0 };
|
|
461
565
|
this.retainedPanels.clear();
|
|
462
566
|
this.registry = [];
|
|
567
|
+
this._recentlyOpened = [];
|
|
463
568
|
this._focusedPane = 'top';
|
|
569
|
+
this._focusTarget = 'prompt';
|
|
464
570
|
this._bottomPaneVisible = false;
|
|
465
571
|
this._visible = false;
|
|
466
572
|
this._invalidateWorkspaceTabs();
|
|
@@ -543,11 +649,12 @@ export class PanelManager {
|
|
|
543
649
|
}
|
|
544
650
|
|
|
545
651
|
private _getRegistration(panelId: string): PanelRegistration | undefined {
|
|
546
|
-
|
|
652
|
+
const resolvedId = this._resolveId(panelId);
|
|
653
|
+
return this.registry.find((registration) => registration.id === resolvedId);
|
|
547
654
|
}
|
|
548
655
|
|
|
549
656
|
private _shouldRetain(panelId: string): boolean {
|
|
550
|
-
return this._getRegistration(panelId)?.
|
|
657
|
+
return this._getRegistration(panelId)?.retainOnClose === true;
|
|
551
658
|
}
|
|
552
659
|
|
|
553
660
|
private _activateByIdInPane(panelId: string, which: 'top' | 'bottom'): void {
|