@pellux/goodvibes-tui 1.1.0 → 1.7.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 +85 -21
- package/README.md +20 -11
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/conversation-rendering.ts +19 -0
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +36 -0
- package/src/input/command-registry.ts +38 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +15 -7
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-feed-routes.ts +61 -23
- package/src/input/handler-feed.ts +47 -23
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +6 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +24 -27
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +9 -43
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +151 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +13 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/turn-injection.ts +114 -0
- package/src/runtime/bootstrap-command-context.ts +21 -1
- package/src/runtime/bootstrap-command-parts.ts +34 -7
- package/src/runtime/bootstrap-core.ts +14 -4
- package/src/runtime/bootstrap-shell.ts +29 -16
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConfigModalAction,
|
|
3
|
+
ConfigModalActionContext,
|
|
4
|
+
ConfigModalRow,
|
|
5
|
+
ConfigModalSurface,
|
|
6
|
+
ConfigModalTab,
|
|
7
|
+
ConfigModalView,
|
|
8
|
+
} from './config-modal-types.ts';
|
|
9
|
+
import type { ModalSectionStyle } from '../renderer/modal-factory.ts';
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
ConfigModalAction,
|
|
13
|
+
ConfigModalActionContext,
|
|
14
|
+
ConfigModalRow,
|
|
15
|
+
ConfigModalSurface,
|
|
16
|
+
ConfigModalTab,
|
|
17
|
+
ConfigModalView,
|
|
18
|
+
} from './config-modal-types.ts';
|
|
19
|
+
|
|
20
|
+
/** A tab as the renderer sees it (structure frozen, label live). */
|
|
21
|
+
export interface ConfigModalRenderTab {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly active: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A row as the renderer sees it: frozen identity, live label/value overlay. */
|
|
28
|
+
export interface ConfigModalRenderRow {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly label: string;
|
|
31
|
+
readonly style?: ModalSectionStyle;
|
|
32
|
+
readonly selected: boolean;
|
|
33
|
+
readonly selectable: boolean;
|
|
34
|
+
/** True when this frozen row has no live counterpart this tick (value went
|
|
35
|
+
* stale — kept in place, dimmed, until the next interaction boundary). */
|
|
36
|
+
readonly stale: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Everything renderConfigModal needs — computed by overlaying live values onto
|
|
40
|
+
* the frozen structure so layout stays stable between key presses. */
|
|
41
|
+
export interface ConfigModalRenderModel {
|
|
42
|
+
readonly title: string;
|
|
43
|
+
readonly tabs: readonly ConfigModalRenderTab[];
|
|
44
|
+
readonly header: readonly string[];
|
|
45
|
+
readonly rows: readonly ConfigModalRenderRow[];
|
|
46
|
+
readonly emptyText?: string;
|
|
47
|
+
readonly degraded?: string;
|
|
48
|
+
readonly status?: string;
|
|
49
|
+
readonly hints: readonly string[];
|
|
50
|
+
readonly scroll: { readonly offset: number; readonly total: number; readonly visible: number };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const DEFAULT_VISIBLE_ROWS = 10;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* ConfigModal — the single, generic, named config-modal host. One instance
|
|
57
|
+
* lives on the InputHandler (like `settingsModal`); it renders whatever
|
|
58
|
+
* `ConfigModalSurface` is currently open. Key routing is a single
|
|
59
|
+
* `handleConfigModalToken` path (handler-modal-routes.ts) — no parallel input
|
|
60
|
+
* system. See config-modal-types.ts for the surface contract and the liveness
|
|
61
|
+
* doctrine this class enforces.
|
|
62
|
+
*/
|
|
63
|
+
export class ConfigModal {
|
|
64
|
+
public active = false;
|
|
65
|
+
|
|
66
|
+
private surface: ConfigModalSurface | null = null;
|
|
67
|
+
private requestRender: () => void = () => {};
|
|
68
|
+
|
|
69
|
+
/** Active tab / selected row tracked by STABLE id (survives value refresh). */
|
|
70
|
+
private activeTabId = '';
|
|
71
|
+
private selectedRowId = '';
|
|
72
|
+
|
|
73
|
+
/** Transient status line (action result, error, or confirm prompt). */
|
|
74
|
+
private statusMessage = '';
|
|
75
|
+
/** Pending destructive confirm: the action key awaiting a second press. */
|
|
76
|
+
private pendingConfirmKey: string | null = null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Structure captured at the last interaction boundary (open / key press).
|
|
80
|
+
* Renders overlay live values onto THIS so rows never reflow mid-interaction.
|
|
81
|
+
*/
|
|
82
|
+
private frozenView: ConfigModalView | null = null;
|
|
83
|
+
|
|
84
|
+
private scrollOffset = 0;
|
|
85
|
+
private visibleRows = DEFAULT_VISIBLE_ROWS;
|
|
86
|
+
|
|
87
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
open(surface: ConfigModalSurface, requestRender: () => void = () => {}): void {
|
|
90
|
+
// Re-opening a different surface closes the previous one cleanly.
|
|
91
|
+
if (this.surface && this.surface !== surface) this.surface.onClose?.();
|
|
92
|
+
this.surface = surface;
|
|
93
|
+
this.requestRender = requestRender;
|
|
94
|
+
this.active = true;
|
|
95
|
+
this.statusMessage = '';
|
|
96
|
+
this.pendingConfirmKey = null;
|
|
97
|
+
this.scrollOffset = 0;
|
|
98
|
+
const view = surface.buildView();
|
|
99
|
+
this.frozenView = view;
|
|
100
|
+
this.activeTabId = view.tabs[0]?.id ?? '';
|
|
101
|
+
this.selectedRowId = this._firstSelectableId(this._frozenTab());
|
|
102
|
+
surface.onOpen?.(requestRender);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
close(): void {
|
|
106
|
+
if (!this.active) return;
|
|
107
|
+
this.surface?.onClose?.();
|
|
108
|
+
this.active = false;
|
|
109
|
+
this.surface = null;
|
|
110
|
+
this.frozenView = null;
|
|
111
|
+
this.statusMessage = '';
|
|
112
|
+
this.pendingConfirmKey = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Re-activate the current surface after a nested modal closes (Esc stack). */
|
|
116
|
+
reopen(): void {
|
|
117
|
+
if (!this.surface) return;
|
|
118
|
+
this.active = true;
|
|
119
|
+
this.syncStructure();
|
|
120
|
+
this.surface.onOpen?.(this.requestRender);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getSurfaceName(): string | null {
|
|
124
|
+
return this.surface?.name ?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setViewportRows(rows: number): void {
|
|
128
|
+
this.visibleRows = Math.max(3, rows);
|
|
129
|
+
this._clampScroll();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Interaction boundary ────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Re-capture the structure from the current live view. Called on every key
|
|
136
|
+
* press (never during a pure render tick) — this is what makes structural
|
|
137
|
+
* changes appear only at an interaction boundary. Selection + active tab are
|
|
138
|
+
* preserved by id and clamped if their target vanished.
|
|
139
|
+
*/
|
|
140
|
+
syncStructure(): void {
|
|
141
|
+
if (!this.surface) return;
|
|
142
|
+
const view = this.surface.buildView();
|
|
143
|
+
this.frozenView = view;
|
|
144
|
+
if (!view.tabs.some((t) => t.id === this.activeTabId)) {
|
|
145
|
+
this.activeTabId = view.tabs[0]?.id ?? '';
|
|
146
|
+
}
|
|
147
|
+
const tab = this._frozenTab();
|
|
148
|
+
if (!this._selectableIds(tab).includes(this.selectedRowId)) {
|
|
149
|
+
this.selectedRowId = this._firstSelectableId(tab);
|
|
150
|
+
}
|
|
151
|
+
this._clampScroll();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Navigation (each is an interaction boundary) ────────────────────────────
|
|
155
|
+
|
|
156
|
+
moveDown(): void {
|
|
157
|
+
this._clearConfirm();
|
|
158
|
+
this.syncStructure();
|
|
159
|
+
const ids = this._selectableIds(this._frozenTab());
|
|
160
|
+
if (ids.length === 0) return;
|
|
161
|
+
const i = ids.indexOf(this.selectedRowId);
|
|
162
|
+
this.selectedRowId = ids[(i + 1) % ids.length]!;
|
|
163
|
+
this._clampScroll();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
moveUp(): void {
|
|
167
|
+
this._clearConfirm();
|
|
168
|
+
this.syncStructure();
|
|
169
|
+
const ids = this._selectableIds(this._frozenTab());
|
|
170
|
+
if (ids.length === 0) return;
|
|
171
|
+
const i = ids.indexOf(this.selectedRowId);
|
|
172
|
+
this.selectedRowId = ids[(i - 1 + ids.length) % ids.length]!;
|
|
173
|
+
this._clampScroll();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
nextTab(): void {
|
|
177
|
+
this._switchTab(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
prevTab(): void {
|
|
181
|
+
this._switchTab(-1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private _switchTab(dir: 1 | -1): void {
|
|
185
|
+
this._clearConfirm();
|
|
186
|
+
this.syncStructure();
|
|
187
|
+
const tabs = this.frozenView?.tabs ?? [];
|
|
188
|
+
if (tabs.length <= 1) return;
|
|
189
|
+
const i = tabs.findIndex((t) => t.id === this.activeTabId);
|
|
190
|
+
const next = tabs[(i + dir + tabs.length) % tabs.length]!;
|
|
191
|
+
this.activeTabId = next.id;
|
|
192
|
+
this.selectedRowId = this._firstSelectableId(next);
|
|
193
|
+
this.scrollOffset = 0;
|
|
194
|
+
this.statusMessage = '';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Actions ─────────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/** The action bound to `key` on the active tab/row, if enabled. */
|
|
200
|
+
resolveAction(key: string): ConfigModalAction | null {
|
|
201
|
+
const actions = this.surface?.actions ?? [];
|
|
202
|
+
const row = this.getSelectedRow();
|
|
203
|
+
for (const action of actions) {
|
|
204
|
+
if (action.key !== key) continue;
|
|
205
|
+
if (action.enabledFor && !action.enabledFor(row, this.activeTabId)) return null;
|
|
206
|
+
return action;
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Attempt to fire the action bound to `key`. Returns true if a key was an
|
|
213
|
+
* action (consumed). Handles the two-press confirm for destructive actions.
|
|
214
|
+
* This is an interaction boundary (syncs structure first).
|
|
215
|
+
*/
|
|
216
|
+
fireAction(key: string, ctx: Omit<ConfigModalActionContext, 'row' | 'tabId' | 'setStatus' | 'close' | 'requestRender'>): boolean {
|
|
217
|
+
this.syncStructure();
|
|
218
|
+
const action = this.resolveAction(key);
|
|
219
|
+
if (!action) return false;
|
|
220
|
+
if (action.confirm && this.pendingConfirmKey !== key) {
|
|
221
|
+
this.pendingConfirmKey = key;
|
|
222
|
+
this.statusMessage = `Press ${key} again to ${action.label}.`;
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
this._clearConfirm();
|
|
226
|
+
const fullCtx: ConfigModalActionContext = {
|
|
227
|
+
row: this.getSelectedRow(),
|
|
228
|
+
tabId: this.activeTabId,
|
|
229
|
+
print: ctx.print,
|
|
230
|
+
executeCommand: ctx.executeCommand,
|
|
231
|
+
openModal: ctx.openModal,
|
|
232
|
+
requestRender: this.requestRender,
|
|
233
|
+
setStatus: (m: string) => { this.statusMessage = m; },
|
|
234
|
+
close: () => this.close(),
|
|
235
|
+
};
|
|
236
|
+
this.surface?.onAction?.(action.id, fullCtx);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Clear a pending confirm when the user navigates or presses an unrelated key. */
|
|
241
|
+
clearConfirmOnMiss(): void {
|
|
242
|
+
this._clearConfirm();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── Read accessors (for the renderer + tests) ───────────────────────────────
|
|
246
|
+
|
|
247
|
+
getSelectedRow(): ConfigModalRow | null {
|
|
248
|
+
const tab = this._liveTab(this.activeTabId) ?? this._frozenTab();
|
|
249
|
+
return tab?.rows.find((r) => r.id === this.selectedRowId) ?? null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
getActiveTabId(): string {
|
|
253
|
+
return this.activeTabId;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getSelectedRowId(): string {
|
|
257
|
+
return this.selectedRowId;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
getStatusMessage(): string {
|
|
261
|
+
return this.statusMessage;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Compute the render model: frozen structure + live value overlay. Pure —
|
|
266
|
+
* does NOT sync structure, so calling it repeatedly with only value mutations
|
|
267
|
+
* yields byte-identical layout (the liveness contract).
|
|
268
|
+
*/
|
|
269
|
+
getRenderModel(): ConfigModalRenderModel {
|
|
270
|
+
const live = this.surface?.buildView() ?? null;
|
|
271
|
+
const frozen = this.frozenView;
|
|
272
|
+
if (!frozen) {
|
|
273
|
+
return { title: '', tabs: [], header: [], rows: [], hints: [], scroll: { offset: 0, total: 0, visible: this.visibleRows } };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const tabs: ConfigModalRenderTab[] = frozen.tabs.map((ft) => {
|
|
277
|
+
const lt = live?.tabs.find((t) => t.id === ft.id);
|
|
278
|
+
return { id: ft.id, label: lt?.label ?? ft.label, active: ft.id === this.activeTabId };
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const frozenTab = frozen.tabs.find((t) => t.id === this.activeTabId) ?? frozen.tabs[0];
|
|
282
|
+
const liveTab = live?.tabs.find((t) => t.id === this.activeTabId);
|
|
283
|
+
|
|
284
|
+
// Header: all-or-nothing live overlay. Same line count → use live values;
|
|
285
|
+
// a count change is a structural change, deferred (keep frozen header).
|
|
286
|
+
const frozenHeader = frozenTab?.header ?? [];
|
|
287
|
+
const liveHeader = liveTab?.header ?? [];
|
|
288
|
+
const header = liveHeader.length === frozenHeader.length ? liveHeader : frozenHeader;
|
|
289
|
+
|
|
290
|
+
// Rows: iterate the frozen id order; overlay the live row (value refresh) by
|
|
291
|
+
// id, or keep the frozen row marked stale when its live counterpart is gone.
|
|
292
|
+
const frozenRows = frozenTab?.rows ?? [];
|
|
293
|
+
const allRows: ConfigModalRenderRow[] = frozenRows.map((fr) => {
|
|
294
|
+
const lr = liveTab?.rows.find((r) => r.id === fr.id);
|
|
295
|
+
const src = lr ?? fr;
|
|
296
|
+
return {
|
|
297
|
+
id: fr.id,
|
|
298
|
+
label: src.label,
|
|
299
|
+
style: src.style,
|
|
300
|
+
selected: fr.id === this.selectedRowId,
|
|
301
|
+
selectable: fr.selectable !== false,
|
|
302
|
+
stale: lr === undefined,
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const visible = this.visibleRows;
|
|
307
|
+
const windowed = allRows.slice(this.scrollOffset, this.scrollOffset + visible);
|
|
308
|
+
|
|
309
|
+
const hints = [
|
|
310
|
+
...(frozen.hints ?? []),
|
|
311
|
+
...(frozenTab?.hints ?? []),
|
|
312
|
+
...this._actionHints(),
|
|
313
|
+
'Esc close',
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
title: live?.title ?? frozen.title,
|
|
318
|
+
tabs,
|
|
319
|
+
header,
|
|
320
|
+
rows: windowed,
|
|
321
|
+
emptyText: frozenRows.length === 0 ? (frozenTab?.emptyText ?? 'Nothing to show.') : undefined,
|
|
322
|
+
degraded: live?.degraded ?? frozen.degraded,
|
|
323
|
+
status: this.statusMessage || undefined,
|
|
324
|
+
hints,
|
|
325
|
+
scroll: { offset: this.scrollOffset, total: allRows.length, visible },
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ── internals ────────────────────────────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
private _actionHints(): string[] {
|
|
332
|
+
const actions = this.surface?.actions ?? [];
|
|
333
|
+
const row = this.getSelectedRow();
|
|
334
|
+
const out: string[] = [];
|
|
335
|
+
for (const a of actions) {
|
|
336
|
+
if (a.enabledFor && !a.enabledFor(row, this.activeTabId)) continue;
|
|
337
|
+
out.push(`${a.key} ${a.label}`);
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private _clearConfirm(): void {
|
|
343
|
+
if (this.pendingConfirmKey !== null) {
|
|
344
|
+
this.pendingConfirmKey = null;
|
|
345
|
+
this.statusMessage = '';
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private _frozenTab(): ConfigModalTab | undefined {
|
|
350
|
+
return this.frozenView?.tabs.find((t) => t.id === this.activeTabId) ?? this.frozenView?.tabs[0];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private _liveTab(id: string): ConfigModalTab | undefined {
|
|
354
|
+
return this.surface?.buildView().tabs.find((t) => t.id === id);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private _selectableIds(tab: ConfigModalTab | undefined): string[] {
|
|
358
|
+
return (tab?.rows ?? []).filter((r) => r.selectable !== false).map((r) => r.id);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private _firstSelectableId(tab: ConfigModalTab | undefined): string {
|
|
362
|
+
return this._selectableIds(tab)[0] ?? '';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private _clampScroll(): void {
|
|
366
|
+
const tab = this._frozenTab();
|
|
367
|
+
const rows = tab?.rows ?? [];
|
|
368
|
+
const idx = rows.findIndex((r) => r.id === this.selectedRowId);
|
|
369
|
+
const visible = Math.max(3, this.visibleRows);
|
|
370
|
+
if (idx >= 0) {
|
|
371
|
+
if (idx < this.scrollOffset) this.scrollOffset = idx;
|
|
372
|
+
else if (idx >= this.scrollOffset + visible) this.scrollOffset = idx - visible + 1;
|
|
373
|
+
}
|
|
374
|
+
const maxOffset = Math.max(0, rows.length - visible);
|
|
375
|
+
this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxOffset));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -22,15 +22,13 @@ import type { SelectionResult } from './selection-modal.ts';
|
|
|
22
22
|
import type { SearchManager } from './search.ts';
|
|
23
23
|
import type { InputHistory, HistorySearch } from './input-history.ts';
|
|
24
24
|
import type { ConversationManager } from '../core/conversation';
|
|
25
|
-
import type { ProcessModal } from '../renderer/process-modal.ts';
|
|
26
|
-
import type { LiveTailModal } from '../renderer/live-tail-modal.ts';
|
|
27
25
|
import type { BlockActionsMenu } from '../renderer/block-actions.ts';
|
|
28
|
-
import type { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
|
|
29
26
|
import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
30
27
|
import type { BookmarkModal } from './bookmark-modal.ts';
|
|
31
28
|
import type { SettingsModal } from './settings-modal.ts';
|
|
32
29
|
import type { McpWorkspace } from './mcp-workspace.ts';
|
|
33
30
|
import type { SessionPickerModal } from './session-picker-modal.ts';
|
|
31
|
+
import type { ConfigModal } from './config-modal.ts';
|
|
34
32
|
import type { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
35
33
|
import type { OnboardingWizardController } from './onboarding/onboarding-wizard.ts';
|
|
36
34
|
import type { WrappedPromptInfo } from './handler-prompt-buffer.ts';
|
|
@@ -40,6 +38,7 @@ import type { KeybindingsManager } from './keybindings.ts';
|
|
|
40
38
|
import type { ModelPickerTarget } from './model-picker.ts';
|
|
41
39
|
import type { KillRing } from './kill-ring.ts';
|
|
42
40
|
import type { PanelMouseLayout } from './handler-feed-routes.ts';
|
|
41
|
+
import type { FocusTracker } from '../core/focus-tracker.ts';
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
44
|
* Initial mutable scalar values for InputFeedContext.
|
|
@@ -82,8 +81,7 @@ export interface FeedContextMutableInit {
|
|
|
82
81
|
* - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
|
|
83
82
|
* - `selectionModal`, `bookmarkModal`, `settingsModal`, `sessionPickerModal`,
|
|
84
83
|
* `profilePickerModal` — modal objects constructed once
|
|
85
|
-
* - `filePicker`, `modelPicker`, `
|
|
86
|
-
* `agentDetailModal`, `contextInspectorModal`, `blockActionsMenu`,
|
|
84
|
+
* - `filePicker`, `modelPicker`, `contextInspectorModal`, `blockActionsMenu`,
|
|
87
85
|
* `searchManager`, `historySearch`, `onboardingWizard` — service objects constructed once
|
|
88
86
|
* - `panelManager`, `keybindingsManager` — from uiServices, stable
|
|
89
87
|
* - `modalStack` — reference to the handler's shared array
|
|
@@ -91,6 +89,8 @@ export interface FeedContextMutableInit {
|
|
|
91
89
|
* - `commandRegistry`, `commandContext`, `autocomplete`, `inputHistory`,
|
|
92
90
|
* `conversationManager` — wired after construction; synced at feed() entry only
|
|
93
91
|
* (not per-action) since no in-feed action changes them
|
|
92
|
+
* - `focusTracker` (W2.3) — shared instance from uiServices.platform, mutated
|
|
93
|
+
* directly by feedInputTokens() on 'focus' tokens; never reallocated
|
|
94
94
|
*
|
|
95
95
|
* **Rationale:** per-feed mutation on a single object avoids per-keystroke GC pressure
|
|
96
96
|
* from ~80-field object allocation. Stable references are service handles that never
|
|
@@ -107,6 +107,7 @@ export interface FeedContextStableRefs {
|
|
|
107
107
|
mcpWorkspace: McpWorkspace;
|
|
108
108
|
sessionPickerModal: SessionPickerModal;
|
|
109
109
|
profilePickerModal: ProfilePickerModal;
|
|
110
|
+
configModal: ConfigModal;
|
|
110
111
|
historySearch: HistorySearch;
|
|
111
112
|
commandRegistry: CommandRegistry | null;
|
|
112
113
|
commandContext: CommandContext | undefined;
|
|
@@ -114,9 +115,6 @@ export interface FeedContextStableRefs {
|
|
|
114
115
|
filePicker: FilePickerModal;
|
|
115
116
|
modelPicker: ModelPickerModal;
|
|
116
117
|
onboardingWizard: OnboardingWizardController;
|
|
117
|
-
processModal: ProcessModal;
|
|
118
|
-
liveTailModal: LiveTailModal;
|
|
119
|
-
agentDetailModal: AgentDetailModal;
|
|
120
118
|
contextInspectorModal: ContextInspectorModal;
|
|
121
119
|
blockActionsMenu: BlockActionsMenu;
|
|
122
120
|
searchManager: SearchManager;
|
|
@@ -126,6 +124,7 @@ export interface FeedContextStableRefs {
|
|
|
126
124
|
panelManager: PanelManager;
|
|
127
125
|
keybindingsManager: KeybindingsManager;
|
|
128
126
|
killRing: KillRing;
|
|
127
|
+
focusTracker: FocusTracker;
|
|
129
128
|
getHistory: () => InfiniteBuffer;
|
|
130
129
|
getViewportHeight: () => number;
|
|
131
130
|
getScrollTop: () => number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadSkillByTrigger } from '@pellux/goodvibes-sdk/platform/tools';
|
|
2
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
2
3
|
import type { CommandContext, CommandRegistry } from './command-registry.ts';
|
|
3
4
|
import type { AutocompleteEngine } from './autocomplete.ts';
|
|
4
5
|
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
|
|
@@ -92,25 +93,34 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
|
|
|
92
93
|
const commandPromise = state.commandRegistry.get(name)
|
|
93
94
|
? state.commandRegistry.execute(name, args, ctx)
|
|
94
95
|
: (ctx.executeCommand?.(name, args) ?? Promise.resolve(false));
|
|
95
|
-
commandPromise
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} else {
|
|
99
|
-
const shellPaths = state.commandContext?.workspace.shellPaths;
|
|
100
|
-
const skillContent = shellPaths
|
|
101
|
-
? loadSkillByTrigger('/' + name, {
|
|
102
|
-
workingDirectory: shellPaths.workingDirectory,
|
|
103
|
-
homeDirectory: shellPaths.homeDirectory,
|
|
104
|
-
})
|
|
105
|
-
: null;
|
|
106
|
-
if (skillContent) {
|
|
107
|
-
state.commandContext?.submitInput?.(skillContent);
|
|
108
|
-
} else {
|
|
109
|
-
state.conversationManager?.log(`Unknown command: /${name}. Type /help for available commands.`, { fg: '#ef4444' });
|
|
96
|
+
commandPromise
|
|
97
|
+
.then((handled) => {
|
|
98
|
+
if (handled) {
|
|
110
99
|
state.requestRender();
|
|
100
|
+
} else {
|
|
101
|
+
const shellPaths = state.commandContext?.workspace.shellPaths;
|
|
102
|
+
const skillContent = shellPaths
|
|
103
|
+
? loadSkillByTrigger('/' + name, {
|
|
104
|
+
workingDirectory: shellPaths.workingDirectory,
|
|
105
|
+
homeDirectory: shellPaths.homeDirectory,
|
|
106
|
+
})
|
|
107
|
+
: null;
|
|
108
|
+
if (skillContent) {
|
|
109
|
+
state.commandContext?.submitInput?.(skillContent);
|
|
110
|
+
} else {
|
|
111
|
+
state.conversationManager?.log(`Unknown command: /${name}. Type /help for available commands.`, { fg: '#ef4444' });
|
|
112
|
+
state.requestRender();
|
|
113
|
+
}
|
|
111
114
|
}
|
|
112
|
-
}
|
|
113
|
-
|
|
115
|
+
})
|
|
116
|
+
.catch((err: unknown) => {
|
|
117
|
+
// Defense in depth for EVERY command, not just the ones that
|
|
118
|
+
// already guard their own internals: a handler that throws or
|
|
119
|
+
// awaits a rejected promise must never become a silent unhandled
|
|
120
|
+
// rejection — it renders the same way an unknown command does.
|
|
121
|
+
state.conversationManager?.log(`Command /${name} failed: ${summarizeError(err)}`, { fg: '#ef4444' });
|
|
122
|
+
state.requestRender();
|
|
123
|
+
});
|
|
114
124
|
} else {
|
|
115
125
|
closeCommandMode();
|
|
116
126
|
}
|
|
@@ -31,14 +31,26 @@ export type PanelFocusRouteState = {
|
|
|
31
31
|
cyclePanelTab: (direction: 'next' | 'prev') => void;
|
|
32
32
|
onPanelInputConsumed?: (activePanel: import('../panels/types.ts').Panel | null, key: string) => void;
|
|
33
33
|
/**
|
|
34
|
-
* True when
|
|
35
|
-
* one
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
34
|
+
* True when THIS token is a paste: a single 'text' token whose value holds
|
|
35
|
+
* more than one character. The SDK tokenizer emits a bracketed paste
|
|
36
|
+
* (\x1b[?2004h, enabled in main.ts init) as exactly one such token; discrete
|
|
37
|
+
* keystrokes — even several batched into one feed() by render-tick latency —
|
|
38
|
+
* arrive as separate 1-char tokens. See the text-token branch below.
|
|
39
|
+
*/
|
|
40
|
+
isPasteToken: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* One-shot hint emitted when a paste is dropped because the focused panel has
|
|
43
|
+
* no text capture (Invariant A: focus must not silently flip to the composer).
|
|
44
|
+
*/
|
|
45
|
+
onPasteDropped?: (panelName: string) => void;
|
|
46
|
+
/**
|
|
47
|
+
* True while a turn is actively streaming; gates Escape's cancel-first
|
|
48
|
+
* precedence (I6.1) ahead of the panel's own two-stage escape contract
|
|
39
49
|
* below.
|
|
40
50
|
*/
|
|
41
|
-
|
|
51
|
+
isTurnActive: () => boolean;
|
|
52
|
+
/** Cancel the active turn. Wired to commandContext.cancelGeneration. */
|
|
53
|
+
cancelGeneration: () => void;
|
|
42
54
|
};
|
|
43
55
|
|
|
44
56
|
export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputToken): {
|
|
@@ -75,6 +87,25 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
75
87
|
// (e.g. dismiss a confirm dialog or clear search). Only unfocus if the
|
|
76
88
|
// panel returns false (unconsumed) or there is no active panel.
|
|
77
89
|
if (token.logicalName === 'escape') {
|
|
90
|
+
// I6.1: while a turn is streaming, a focused panel must not be able to
|
|
91
|
+
// swallow the only way to cancel it (replay R5) — Escape's first job is
|
|
92
|
+
// always cancel-turn. The panel's own two-stage consume-or-unfocus
|
|
93
|
+
// contract below only runs once no turn is active, so a *second*
|
|
94
|
+
// Escape (now with the turn already cancelled) falls through to it
|
|
95
|
+
// normally. panelFocused is left unchanged here (panel stays open and
|
|
96
|
+
// focused) — the panel close moves to that second Escape, or to
|
|
97
|
+
// Ctrl+X (panel-close, keybindings.ts), which is handled earlier in
|
|
98
|
+
// handleGlobalShortcutToken and is unaffected by this branch. This
|
|
99
|
+
// deliberately calls cancelGeneration() directly instead of routing
|
|
100
|
+
// through the shared handleEscape() (handler-modal-stack.ts) — that
|
|
101
|
+
// function's fallthrough order (modal-pop -> active-modal-close ->
|
|
102
|
+
// clear-nonempty-prompt -> cancelGeneration) is not guaranteed to reach
|
|
103
|
+
// cancelGeneration first in every state combination.
|
|
104
|
+
if (state.isTurnActive()) {
|
|
105
|
+
state.cancelGeneration();
|
|
106
|
+
state.requestRender();
|
|
107
|
+
return { handled: true, panelFocused };
|
|
108
|
+
}
|
|
78
109
|
const activePanel = state.panelManager.getActive();
|
|
79
110
|
const panelConsumedEscape = activePanel?.handleInput?.('escape') ?? false;
|
|
80
111
|
if (panelConsumedEscape) {
|
|
@@ -122,18 +153,19 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
122
153
|
|
|
123
154
|
if (token.type === 'text' && token.value) {
|
|
124
155
|
const activePanel = state.panelManager.getActive();
|
|
125
|
-
// A
|
|
126
|
-
// a
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
156
|
+
// Invariant A/B (W6.2). A paste (isPasteToken: one multi-char text token)
|
|
157
|
+
// into a focused text-capturing panel (a `/`-search or steer-draft field —
|
|
158
|
+
// isCapturingTextBurst) is forwarded verbatim below; into any other focused
|
|
159
|
+
// panel it is DROPPED with a one-shot hint — never exploded into per-char
|
|
160
|
+
// hotkeys, and never a silent focus flip to the composer (what the old
|
|
161
|
+
// per-feed char-sum burst guard did, which also misfired on two quick nav
|
|
162
|
+
// keys in one feed). Focus moves only on an explicit transfer verb, so
|
|
163
|
+
// panelFocused is left unchanged. Discrete 1-char keystrokes are not pastes
|
|
164
|
+
// and fall through to the per-char dispatch below, one at a time.
|
|
165
|
+
if (state.isPasteToken && !activePanel?.isCapturingTextBurst?.()) {
|
|
166
|
+
state.onPasteDropped?.(activePanel?.name ?? 'the panel');
|
|
135
167
|
state.requestRender();
|
|
136
|
-
return { handled:
|
|
168
|
+
return { handled: true, panelFocused };
|
|
137
169
|
}
|
|
138
170
|
if (activePanel?.handleInput) {
|
|
139
171
|
for (const ch of token.value) {
|
|
@@ -150,7 +182,8 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
150
182
|
export type IndicatorFocusRouteState = {
|
|
151
183
|
indicatorFocused: boolean;
|
|
152
184
|
modalOpened: (name: string) => void;
|
|
153
|
-
|
|
185
|
+
/** W2.2: the footer process indicator's [Enter] opens the Fleet panel (F2 also opens the Fleet panel; the retired process modal was removed in W6.2). */
|
|
186
|
+
openFleetPanel: () => void;
|
|
154
187
|
requestRender: () => void;
|
|
155
188
|
};
|
|
156
189
|
|
|
@@ -171,8 +204,9 @@ export function handleIndicatorFocusToken(state: IndicatorFocusRouteState, token
|
|
|
171
204
|
}
|
|
172
205
|
if (token.logicalName === 'enter') {
|
|
173
206
|
indicatorFocused = false;
|
|
174
|
-
|
|
175
|
-
|
|
207
|
+
// W2.2: repointed from the retired process modal (removed in W6.2) to the
|
|
208
|
+
// Fleet panel — F2 also opens the Fleet panel (handlePromptKeyToken f2).
|
|
209
|
+
state.openFleetPanel();
|
|
176
210
|
state.requestRender();
|
|
177
211
|
return { handled: true, indicatorFocused };
|
|
178
212
|
}
|
|
@@ -297,7 +331,8 @@ export type KeyRouteState = {
|
|
|
297
331
|
commandRegistry?: CommandRegistry | null;
|
|
298
332
|
autocomplete: AutocompleteEngine | null;
|
|
299
333
|
blockActionsMenu: { open: (block: BlockMeta) => void };
|
|
300
|
-
|
|
334
|
+
/** W6.2 e: F2 opens+focuses the Fleet panel (which subsumes the retired process modal). */
|
|
335
|
+
openFleetPanel: () => void;
|
|
301
336
|
modalOpened: (name: string) => void;
|
|
302
337
|
saveUndoState: () => void;
|
|
303
338
|
/** Break the undo coalescing group (call on cursor moves). */
|
|
@@ -570,8 +605,11 @@ export function handlePromptKeyToken(state: KeyRouteState, token: InputToken): {
|
|
|
570
605
|
|
|
571
606
|
if (token.logicalName === 'f2') {
|
|
572
607
|
indicatorFocused = false;
|
|
573
|
-
|
|
574
|
-
|
|
608
|
+
// W6.2 e: F2 opens AND focuses the Fleet panel, which subsumes the deleted
|
|
609
|
+
// process modal (ProcessModal/AgentDetailModal/LiveTailModal — removed in
|
|
610
|
+
// W6.1 once this repoint made them unreachable). openFleetPanel sets
|
|
611
|
+
// panelFocused on the shared context directly, so nothing more is returned.
|
|
612
|
+
state.openFleetPanel();
|
|
575
613
|
return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
|
|
576
614
|
}
|
|
577
615
|
|