@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/src/input/handler.ts
CHANGED
|
@@ -140,8 +140,33 @@ export class InputHandler implements InputHandlerLike {
|
|
|
140
140
|
public commandMode = false;
|
|
141
141
|
/** True when the process indicator bar has keyboard focus. */
|
|
142
142
|
public indicatorFocused = false;
|
|
143
|
-
/**
|
|
144
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Fallback focus store used only when the panel manager does not implement
|
|
145
|
+
* focus ownership (lightweight test stubs). Production always delegates to
|
|
146
|
+
* PanelManager, which is the single source of truth.
|
|
147
|
+
*/
|
|
148
|
+
private _panelFocusedFallback = false;
|
|
149
|
+
/**
|
|
150
|
+
* True when keyboard focus is on the active panel (arrow/enter go to panel,
|
|
151
|
+
* not prompt). Ownership lives in PanelManager (focusTarget); this reads and
|
|
152
|
+
* writes through to it so handler focus can never disagree with what the
|
|
153
|
+
* panel workspace actually shows.
|
|
154
|
+
*/
|
|
155
|
+
public get panelFocused(): boolean {
|
|
156
|
+
const pm = this.uiServices.shell.panelManager;
|
|
157
|
+
return typeof pm.getFocusTarget === 'function'
|
|
158
|
+
? pm.getFocusTarget() === 'panel'
|
|
159
|
+
: this._panelFocusedFallback;
|
|
160
|
+
}
|
|
161
|
+
public set panelFocused(value: boolean) {
|
|
162
|
+
const pm = this.uiServices.shell.panelManager;
|
|
163
|
+
if (typeof pm.focusPanels === 'function' && typeof pm.focusPrompt === 'function') {
|
|
164
|
+
if (value) pm.focusPanels();
|
|
165
|
+
else pm.focusPrompt();
|
|
166
|
+
} else {
|
|
167
|
+
this._panelFocusedFallback = value;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
145
170
|
|
|
146
171
|
public tokenizer = new InputTokenizer();
|
|
147
172
|
public pasteRegistry = new Map<string, string>();
|
|
@@ -468,6 +493,15 @@ export class InputHandler implements InputHandlerLike {
|
|
|
468
493
|
};
|
|
469
494
|
|
|
470
495
|
this.requestRender = bufferedRequestRender;
|
|
496
|
+
// Snapshot the overlay flags before the feed: command handlers executed
|
|
497
|
+
// synchronously during token processing (e.g. /shortcuts, /help via
|
|
498
|
+
// ui-openers) mutate these directly on the handler, and an unconditional
|
|
499
|
+
// write-back of the pre-feed snapshot would silently revert them in the
|
|
500
|
+
// same keystroke (the "/shortcuts never displays" defect).
|
|
501
|
+
const helpOverlayActiveBefore = this.helpOverlayActive;
|
|
502
|
+
const helpScrollOffsetBefore = this.helpScrollOffset;
|
|
503
|
+
const shortcutsOverlayActiveBefore = this.shortcutsOverlayActive;
|
|
504
|
+
const shortcutsScrollOffsetBefore = this.shortcutsScrollOffset;
|
|
471
505
|
try {
|
|
472
506
|
const context = this.feedContext;
|
|
473
507
|
// Sync mutable scalars from handler into the reused context.
|
|
@@ -506,10 +540,13 @@ export class InputHandler implements InputHandlerLike {
|
|
|
506
540
|
this.commandMode = context.commandMode;
|
|
507
541
|
this.panelFocused = context.panelFocused;
|
|
508
542
|
this.indicatorFocused = context.indicatorFocused;
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
this.
|
|
543
|
+
// Overlay flags: only apply the pipeline's value when the pipeline
|
|
544
|
+
// itself changed it (e.g. Escape closing the overlay). Otherwise keep
|
|
545
|
+
// the live handler value, which a command handler may have just set.
|
|
546
|
+
if (context.helpOverlayActive !== helpOverlayActiveBefore) this.helpOverlayActive = context.helpOverlayActive;
|
|
547
|
+
if (context.helpScrollOffset !== helpScrollOffsetBefore) this.helpScrollOffset = context.helpScrollOffset;
|
|
548
|
+
if (context.shortcutsOverlayActive !== shortcutsOverlayActiveBefore) this.shortcutsOverlayActive = context.shortcutsOverlayActive;
|
|
549
|
+
if (context.shortcutsScrollOffset !== shortcutsScrollOffsetBefore) this.shortcutsScrollOffset = context.shortcutsScrollOffset;
|
|
513
550
|
this.selectionCallback = context.selectionCallback;
|
|
514
551
|
this.nextPasteId = context.nextPasteId;
|
|
515
552
|
this.nextImageId = context.nextImageId;
|
package/src/input/keybindings.ts
CHANGED
|
@@ -38,6 +38,17 @@ export type KeyAction =
|
|
|
38
38
|
| 'panel-close-all'
|
|
39
39
|
| 'panel-tab-next'
|
|
40
40
|
| 'panel-tab-prev'
|
|
41
|
+
| 'panel-tab-1'
|
|
42
|
+
| 'panel-tab-2'
|
|
43
|
+
| 'panel-tab-3'
|
|
44
|
+
| 'panel-tab-4'
|
|
45
|
+
| 'panel-tab-5'
|
|
46
|
+
| 'panel-tab-6'
|
|
47
|
+
| 'panel-tab-7'
|
|
48
|
+
| 'panel-tab-8'
|
|
49
|
+
| 'panel-tab-9'
|
|
50
|
+
| 'panel-ops'
|
|
51
|
+
| 'panel-focus-toggle'
|
|
41
52
|
| 'history-search'
|
|
42
53
|
| 'search'
|
|
43
54
|
| 'block-copy'
|
|
@@ -51,7 +62,6 @@ export type KeyAction =
|
|
|
51
62
|
| 'undo'
|
|
52
63
|
| 'redo'
|
|
53
64
|
| 'paste'
|
|
54
|
-
| 'replay-panel'
|
|
55
65
|
| 'word-back'
|
|
56
66
|
| 'word-forward'
|
|
57
67
|
| 'kill-to-start'
|
|
@@ -69,6 +79,17 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
|
|
|
69
79
|
'panel-close-all': 'Close all open panels',
|
|
70
80
|
'panel-tab-next': 'Next workspace panel tab',
|
|
71
81
|
'panel-tab-prev': 'Previous workspace panel tab',
|
|
82
|
+
'panel-tab-1': 'Jump to workspace panel tab 1',
|
|
83
|
+
'panel-tab-2': 'Jump to workspace panel tab 2',
|
|
84
|
+
'panel-tab-3': 'Jump to workspace panel tab 3',
|
|
85
|
+
'panel-tab-4': 'Jump to workspace panel tab 4',
|
|
86
|
+
'panel-tab-5': 'Jump to workspace panel tab 5',
|
|
87
|
+
'panel-tab-6': 'Jump to workspace panel tab 6',
|
|
88
|
+
'panel-tab-7': 'Jump to workspace panel tab 7',
|
|
89
|
+
'panel-tab-8': 'Jump to workspace panel tab 8',
|
|
90
|
+
'panel-tab-9': 'Jump to workspace panel tab 9',
|
|
91
|
+
'panel-ops': 'Open the Ops Control panel',
|
|
92
|
+
'panel-focus-toggle': 'Switch keyboard focus between top and bottom pane',
|
|
72
93
|
'history-search': 'Reverse input history search',
|
|
73
94
|
'search': 'Toggle conversation search',
|
|
74
95
|
'block-copy': 'Copy nearest block to clipboard',
|
|
@@ -82,7 +103,6 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
|
|
|
82
103
|
'undo': 'Undo last prompt edit',
|
|
83
104
|
'redo': 'Redo last undone edit',
|
|
84
105
|
'paste': 'Paste from clipboard (image priority)',
|
|
85
|
-
'replay-panel': 'Open / close the Replay panel',
|
|
86
106
|
'word-back': 'Move cursor to start of previous word (Alt+B)',
|
|
87
107
|
'word-forward': 'Move cursor to end of next word (Alt+F)',
|
|
88
108
|
'kill-to-start': 'Kill from cursor to start of line into kill ring (Ctrl+U)',
|
|
@@ -101,6 +121,27 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
|
|
|
101
121
|
'panel-close-all': [{ key: 'x', ctrl: true, shift: true }],
|
|
102
122
|
'panel-tab-next': [{ key: ']', ctrl: true }],
|
|
103
123
|
'panel-tab-prev': [{ key: '[', ctrl: true }],
|
|
124
|
+
// Alt+1..9: jump directly to the Nth workspace panel tab (across both panes).
|
|
125
|
+
// The tokenizer delivers Alt as the token's `meta` modifier; comboMatches /
|
|
126
|
+
// lookup treat `meta` as an alias for `alt`, so these alt-combos route through
|
|
127
|
+
// the same rebindable path as every other action.
|
|
128
|
+
'panel-tab-1': [{ key: '1', alt: true }],
|
|
129
|
+
'panel-tab-2': [{ key: '2', alt: true }],
|
|
130
|
+
'panel-tab-3': [{ key: '3', alt: true }],
|
|
131
|
+
'panel-tab-4': [{ key: '4', alt: true }],
|
|
132
|
+
'panel-tab-5': [{ key: '5', alt: true }],
|
|
133
|
+
'panel-tab-6': [{ key: '6', alt: true }],
|
|
134
|
+
'panel-tab-7': [{ key: '7', alt: true }],
|
|
135
|
+
'panel-tab-8': [{ key: '8', alt: true }],
|
|
136
|
+
'panel-tab-9': [{ key: '9', alt: true }],
|
|
137
|
+
// Ctrl+O: open the Ops Control panel (operator intervention console).
|
|
138
|
+
// Routed globally in handleGlobalShortcutToken: prefers commandContext.openOpsPanel()
|
|
139
|
+
// when the operator-control-plane feature flag wired it, else falls back to opening
|
|
140
|
+
// the always-registered 'ops-control' panel type directly via the panel manager.
|
|
141
|
+
'panel-ops': [{ key: 'o', ctrl: true }],
|
|
142
|
+
// Ctrl+G: toggle keyboard focus between the top and bottom panes. Ctrl+G is
|
|
143
|
+
// otherwise unbound in the default table.
|
|
144
|
+
'panel-focus-toggle': [{ key: 'g', ctrl: true }],
|
|
104
145
|
'history-search': [{ key: 'r', ctrl: true }],
|
|
105
146
|
'search': [{ key: 'f', ctrl: true }],
|
|
106
147
|
'block-copy': [{ key: 'y', ctrl: true }],
|
|
@@ -117,7 +158,6 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
|
|
|
117
158
|
'undo': [{ key: 'z', ctrl: true }],
|
|
118
159
|
'redo': [{ key: 'z', ctrl: true, shift: true }],
|
|
119
160
|
'paste': [{ key: 'v', ctrl: true }],
|
|
120
|
-
'replay-panel': [{ key: 'r', ctrl: true, shift: true }],
|
|
121
161
|
// Word navigation (Alt+B / Alt+F — emacs readline standard)
|
|
122
162
|
'word-back': [{ key: 'b', alt: true }],
|
|
123
163
|
'word-forward': [{ key: 'f', alt: true }],
|
|
@@ -240,9 +280,13 @@ export class KeybindingsManager {
|
|
|
240
280
|
* lookup — O(1) keybinding lookup by token.
|
|
241
281
|
* Returns the matching KeyAction, or null if no binding matches.
|
|
242
282
|
*/
|
|
243
|
-
lookup(token: { logicalName?: string; ctrl?: boolean; shift?: boolean; alt?: boolean }): KeyAction | null {
|
|
283
|
+
lookup(token: { logicalName?: string; ctrl?: boolean; shift?: boolean; alt?: boolean; meta?: boolean }): KeyAction | null {
|
|
244
284
|
if (!token.logicalName) return null;
|
|
245
|
-
|
|
285
|
+
// The tokenizer delivers the Alt modifier as `meta`; the binding table stores
|
|
286
|
+
// it as `alt`. Treat the two as one modifier at the matching boundary so
|
|
287
|
+
// Alt-based combos (word-nav, kill-ring, panel-tab-1..9) resolve at runtime.
|
|
288
|
+
const alt = token.alt ?? token.meta;
|
|
289
|
+
const key = `${token.logicalName}:${token.ctrl ? 1 : 0}:${token.shift ? 1 : 0}:${alt ? 1 : 0}`;
|
|
246
290
|
return this.lookupMap.get(key) ?? null;
|
|
247
291
|
}
|
|
248
292
|
|
|
@@ -263,7 +307,7 @@ export class KeybindingsManager {
|
|
|
263
307
|
*/
|
|
264
308
|
matches(
|
|
265
309
|
action: KeyAction,
|
|
266
|
-
token: { logicalName?: string; ctrl?: boolean; shift?: boolean; alt?: boolean },
|
|
310
|
+
token: { logicalName?: string; ctrl?: boolean; shift?: boolean; alt?: boolean; meta?: boolean },
|
|
267
311
|
): boolean {
|
|
268
312
|
const combos = this.bindings[action];
|
|
269
313
|
if (!combos) return false;
|
|
@@ -272,12 +316,13 @@ export class KeybindingsManager {
|
|
|
272
316
|
|
|
273
317
|
private comboMatches(
|
|
274
318
|
combo: KeyCombo,
|
|
275
|
-
token: { logicalName?: string; ctrl?: boolean; shift?: boolean; alt?: boolean },
|
|
319
|
+
token: { logicalName?: string; ctrl?: boolean; shift?: boolean; alt?: boolean; meta?: boolean },
|
|
276
320
|
): boolean {
|
|
277
321
|
if (token.logicalName !== combo.key) return false;
|
|
278
322
|
if (!!combo.ctrl !== !!token.ctrl) return false;
|
|
279
323
|
if (!!combo.shift !== !!token.shift) return false;
|
|
280
|
-
|
|
324
|
+
// Alt arrives as `meta` from the tokenizer; accept either as the Alt modifier.
|
|
325
|
+
if (!!combo.alt !== !!(token.alt ?? token.meta)) return false;
|
|
281
326
|
return true;
|
|
282
327
|
}
|
|
283
328
|
|
|
@@ -92,6 +92,8 @@ export class ModelPickerModal {
|
|
|
92
92
|
public contextCapPendingModel: ModelDefinition | null = null;
|
|
93
93
|
/** Current input string in contextCap mode. */
|
|
94
94
|
public contextCapQuery = '';
|
|
95
|
+
/** Validation error from the last context cap commit (e.g. out-of-range value); null when no error. */
|
|
96
|
+
public contextCapError: string | null = null;
|
|
95
97
|
|
|
96
98
|
// ── Search / filter ─────────────────────────────────────────────────────────────────────────────
|
|
97
99
|
/** Current search query string (empty = no filter). */
|
|
@@ -215,6 +217,7 @@ export class ModelPickerModal {
|
|
|
215
217
|
this.previousMode = 'model';
|
|
216
218
|
this.contextCapPendingModel = model;
|
|
217
219
|
this.contextCapQuery = '';
|
|
220
|
+
this.contextCapError = null;
|
|
218
221
|
this.mode = 'contextCap';
|
|
219
222
|
}
|
|
220
223
|
|
|
@@ -245,6 +248,7 @@ export class ModelPickerModal {
|
|
|
245
248
|
this.query = '';
|
|
246
249
|
this.categoryFilter = 'all';
|
|
247
250
|
this.capabilityFilter = 'none';
|
|
251
|
+
this.availableOnly = true;
|
|
248
252
|
const filtered = this.getFilteredModels();
|
|
249
253
|
const idx = filtered.findIndex(m => m.id === currentModelId);
|
|
250
254
|
this.selectedIndex = idx >= 0 ? idx : 0;
|
|
@@ -310,6 +314,7 @@ export class ModelPickerModal {
|
|
|
310
314
|
this.pendingModel = null;
|
|
311
315
|
this.contextCapPendingModel = null;
|
|
312
316
|
this.contextCapQuery = '';
|
|
317
|
+
this.contextCapError = null;
|
|
313
318
|
this.searchFocused = false;
|
|
314
319
|
this.selectedIndex = 0;
|
|
315
320
|
this.scrollOffset = 0;
|
|
@@ -6,6 +6,10 @@ import { FileExplorerPanel } from '../panels/file-explorer-panel.ts';
|
|
|
6
6
|
import { FilePreviewPanel } from '../panels/file-preview-panel.ts';
|
|
7
7
|
import { SymbolOutlinePanel } from '../panels/symbol-outline-panel.ts';
|
|
8
8
|
import { ApprovalPanel } from '../panels/approval-panel.ts';
|
|
9
|
+
import { TasksPanel } from '../panels/tasks-panel.ts';
|
|
10
|
+
import { OrchestrationPanel } from '../panels/orchestration-panel.ts';
|
|
11
|
+
import { AgentInspectorPanel } from '../panels/agent-inspector-panel.ts';
|
|
12
|
+
import { DiffPanel } from '../panels/diff-panel.ts';
|
|
9
13
|
|
|
10
14
|
function ensurePreviewPanel(panelManager: PanelManager): FilePreviewPanel | null {
|
|
11
15
|
const existing = panelManager.getPanel('preview');
|
|
@@ -24,7 +28,29 @@ function ensurePreviewPanel(panelManager: PanelManager): FilePreviewPanel | null
|
|
|
24
28
|
return opened instanceof FilePreviewPanel ? opened : null;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
// WO-133: shared by explorer's and preview's 'd' (diff) key — same
|
|
32
|
+
// open/focus bridge as ensurePreviewPanel above.
|
|
33
|
+
function ensureDiffPanel(panelManager: PanelManager): DiffPanel | null {
|
|
34
|
+
const existing = panelManager.getPanel('diff');
|
|
35
|
+
if (existing instanceof DiffPanel) {
|
|
36
|
+
const pane = panelManager.getPaneOf('diff');
|
|
37
|
+
panelManager.activateById('diff');
|
|
38
|
+
if (pane) panelManager.focusPane(pane);
|
|
39
|
+
return existing;
|
|
40
|
+
}
|
|
41
|
+
const targetPane: 'top' | 'bottom' = panelManager.isBottomPaneVisible()
|
|
42
|
+
? (panelManager.getFocusedPane() === 'top' ? 'bottom' : 'top')
|
|
43
|
+
: 'bottom';
|
|
44
|
+
const opened = panelManager.open('diff', targetPane);
|
|
45
|
+
panelManager.show();
|
|
46
|
+
panelManager.focusPane(targetPane);
|
|
47
|
+
return opened instanceof DiffPanel ? opened : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Exported so a future preview-reload action (e.g. an explicit "r" reload
|
|
51
|
+
// key) can re-sync the outline against the same file without duplicating
|
|
52
|
+
// this lookup — the panel-integration wiring for that key lands separately.
|
|
53
|
+
export function syncSymbolOutlineFromPreview(panelManager: PanelManager, previewPanel: FilePreviewPanel): void {
|
|
28
54
|
const symbols = panelManager.getPanel('symbols');
|
|
29
55
|
const filePath = previewPanel.getCurrentFilePath();
|
|
30
56
|
const source = previewPanel.getSource();
|
|
@@ -41,6 +67,16 @@ export function handlePanelIntegrationAction(
|
|
|
41
67
|
): boolean {
|
|
42
68
|
if (!activePanel) return false;
|
|
43
69
|
|
|
70
|
+
// Prefer the panel's own integration hook when it provides one. Panels migrated
|
|
71
|
+
// onto the formal hook opt out of the instanceof routing below.
|
|
72
|
+
if (activePanel.handlePanelIntegrationAction) {
|
|
73
|
+
const consumed = activePanel.handlePanelIntegrationAction(key, {
|
|
74
|
+
panelManager,
|
|
75
|
+
executeCommand: commandContext?.executeCommand,
|
|
76
|
+
});
|
|
77
|
+
if (consumed) return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
44
80
|
if ((key === 'enter' || key === 'return' || key === 'right') && activePanel instanceof FileExplorerPanel) {
|
|
45
81
|
const filePath = activePanel.getFocusedFilePath();
|
|
46
82
|
if (!filePath) return false;
|
|
@@ -51,6 +87,44 @@ export function handlePanelIntegrationAction(
|
|
|
51
87
|
return true;
|
|
52
88
|
}
|
|
53
89
|
|
|
90
|
+
// WO-133: 'd' on the explorer diffs the currently focused file, and 'd' on
|
|
91
|
+
// the preview diffs whatever file is currently open — both against HEAD,
|
|
92
|
+
// both via the same DiffPanel.showFileDiffs entry point.
|
|
93
|
+
if (key === 'd' && activePanel instanceof FileExplorerPanel) {
|
|
94
|
+
const filePath = activePanel.getFocusedFilePath();
|
|
95
|
+
if (!filePath) return false;
|
|
96
|
+
const diffPanel = ensureDiffPanel(panelManager);
|
|
97
|
+
if (!diffPanel) return false;
|
|
98
|
+
void diffPanel.showFileDiffs([filePath], 'HEAD').catch((err) => {
|
|
99
|
+
logger.debug('explorer diff dispatch failed', { err });
|
|
100
|
+
});
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (key === 'd' && activePanel instanceof FilePreviewPanel) {
|
|
105
|
+
const filePath = activePanel.getCurrentFilePath();
|
|
106
|
+
if (!filePath) return false;
|
|
107
|
+
const diffPanel = ensureDiffPanel(panelManager);
|
|
108
|
+
if (!diffPanel) return false;
|
|
109
|
+
void diffPanel.showFileDiffs([filePath], 'HEAD').catch((err) => {
|
|
110
|
+
logger.debug('preview diff dispatch failed', { err });
|
|
111
|
+
});
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// WO-133: 'r' on the preview reloads the open file from disk, then
|
|
116
|
+
// re-syncs the symbol outline against the refreshed content (WO-126's
|
|
117
|
+
// async tree-sitter loadFile via syncSymbolOutlineFromPreview above) once
|
|
118
|
+
// the reload actually settles.
|
|
119
|
+
if (key === 'r' && activePanel instanceof FilePreviewPanel) {
|
|
120
|
+
const reloaded = activePanel.consumePendingReload();
|
|
121
|
+
if (!reloaded) return false;
|
|
122
|
+
void reloaded.then(() => syncSymbolOutlineFromPreview(panelManager, activePanel)).catch((err) => {
|
|
123
|
+
logger.debug('preview reload dispatch failed', { err });
|
|
124
|
+
});
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
54
128
|
if ((key === 'enter' || key === 'return') && activePanel instanceof SymbolOutlinePanel) {
|
|
55
129
|
const location = activePanel.getSelectedLocation();
|
|
56
130
|
if (!location) return false;
|
|
@@ -74,5 +148,47 @@ export function handlePanelIntegrationAction(
|
|
|
74
148
|
return true;
|
|
75
149
|
}
|
|
76
150
|
|
|
151
|
+
// WO-131: Enter on a Tasks row — agent-kind tasks jump straight to the Agent
|
|
152
|
+
// Inspector (which owns the deep per-agent timeline); everything else's
|
|
153
|
+
// advertised worktree follow-up is dispatched for real via ctx.executeCommand
|
|
154
|
+
// instead of being printed as a static "/worktree task <task-id>" signpost.
|
|
155
|
+
// w dispatches the task-family posture review the header line advertises.
|
|
156
|
+
if ((key === 'enter' || key === 'return' || key === 'w') && activePanel instanceof TasksPanel) {
|
|
157
|
+
const followUp = activePanel.consumePendingFollowUp();
|
|
158
|
+
if (!followUp) return false;
|
|
159
|
+
if (followUp.kind === 'agent-jump') {
|
|
160
|
+
const inspector = panelManager.open('inspector');
|
|
161
|
+
if (!(inspector instanceof AgentInspectorPanel)) return false;
|
|
162
|
+
inspector.inspectAgent(followUp.agentId);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
if (!commandContext?.executeCommand) return false;
|
|
166
|
+
if (followUp.kind === 'teamwork-review') {
|
|
167
|
+
void commandContext.executeCommand('teamwork', ['review']).catch((err) => {
|
|
168
|
+
logger.debug('tasks panel teamwork review dispatch failed', { err });
|
|
169
|
+
});
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
void commandContext.executeCommand('worktree', ['task', followUp.taskId]).catch((err) => {
|
|
173
|
+
logger.debug('tasks panel worktree review dispatch failed', { err });
|
|
174
|
+
});
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// WO-131: Enter on a node-focused Orchestration row jumps to the Agent
|
|
179
|
+
// Inspector (agent-backed nodes) or the Tasks panel (task-backed nodes).
|
|
180
|
+
if ((key === 'enter' || key === 'return') && activePanel instanceof OrchestrationPanel) {
|
|
181
|
+
const jump = activePanel.consumePendingNodeJump();
|
|
182
|
+
if (!jump) return false;
|
|
183
|
+
if (jump.kind === 'agent-jump') {
|
|
184
|
+
const inspector = panelManager.open('inspector');
|
|
185
|
+
if (!(inspector instanceof AgentInspectorPanel)) return false;
|
|
186
|
+
inspector.inspectAgent(jump.id);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
panelManager.open('tasks');
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
77
193
|
return false;
|
|
78
194
|
}
|
package/src/main.ts
CHANGED
|
@@ -29,7 +29,6 @@ import { applyConversationOverlays } from './renderer/conversation-overlays.ts';
|
|
|
29
29
|
import { buildPanelCompositeData } from './renderer/panel-composite.ts';
|
|
30
30
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
31
31
|
import { registerBuiltinPanels } from './panels/builtin-panels.ts';
|
|
32
|
-
import { renderPanelTabBar } from './renderer/panel-tab-bar.ts';
|
|
33
32
|
import { bootstrapRuntime } from './runtime/bootstrap.ts';
|
|
34
33
|
import type { BootstrapContext } from './runtime/bootstrap.ts';
|
|
35
34
|
import type { HITLMode } from '@pellux/goodvibes-sdk/platform/state';
|
|
@@ -37,6 +36,7 @@ import {
|
|
|
37
36
|
checkRecoveryFile,
|
|
38
37
|
deleteRecoveryFile,
|
|
39
38
|
loadRecoveryConversation,
|
|
39
|
+
readLastSessionPointer,
|
|
40
40
|
writeRecoveryFile,
|
|
41
41
|
} from '@/runtime/index.ts';
|
|
42
42
|
import { handleBlockingShellInput, type PendingPermissionState } from './shell/blocking-input.ts';
|
|
@@ -59,7 +59,7 @@ import { formatUserFacingErrorLine } from './core/format-user-error.ts';
|
|
|
59
59
|
import { wireStreamEventMetrics, type StreamMetrics, type WireStreamEventMetricsResult } from './core/stream-event-wiring.ts';
|
|
60
60
|
import { wireTurnEventHandlers } from './core/turn-event-wiring.ts';
|
|
61
61
|
import { buildContextStatusHint } from './renderer/context-status-hint.ts';
|
|
62
|
-
import { evaluateSessionMaintenance } from '
|
|
62
|
+
import { evaluateSessionMaintenance } from '@/runtime/index.ts';
|
|
63
63
|
|
|
64
64
|
const ALT_SCREEN_ENTER = '\x1b[?1049h'; const ALT_SCREEN_EXIT = '\x1b[?1049l';
|
|
65
65
|
const MOUSE_ENABLE = '\x1b[?1000h\x1b[?1002h\x1b[?1006h'; const MOUSE_DISABLE = '\x1b[?1006l\x1b[?1002l\x1b[?1000l';
|
|
@@ -429,6 +429,7 @@ async function main() {
|
|
|
429
429
|
model: runtime.model,
|
|
430
430
|
provider: runtime.provider,
|
|
431
431
|
toolCount,
|
|
432
|
+
lastSessionId: readLastSessionPointer({ workingDirectory: workingDir, homeDirectory, surfaceRoot: 'tui' }) ?? undefined,
|
|
432
433
|
};
|
|
433
434
|
|
|
434
435
|
const render = () => {
|
|
@@ -491,6 +492,8 @@ async function main() {
|
|
|
491
492
|
provider: runtime.provider,
|
|
492
493
|
contextWindow,
|
|
493
494
|
contextStatusHint,
|
|
495
|
+
// Compact footer posture on short terminals so the shell stays usable.
|
|
496
|
+
compact: height < 30,
|
|
494
497
|
// behavior.autoCompactThreshold is stored as a percent integer (e.g. 80);
|
|
495
498
|
// the meter expects a fraction [0..1]. Clamp to [0,1] to guard nonsense values.
|
|
496
499
|
compactThreshold: Math.min(1, Math.max(0, (configManager.get('behavior.autoCompactThreshold') as number) / 100)),
|