@pellux/goodvibes-tui 1.0.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 +130 -127
- package/README.md +32 -14
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
- package/src/cli/entrypoint.ts +2 -0
- 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 +58 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +104 -2
- package/src/core/stream-stall-watchdog.ts +41 -17
- 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 +126 -8
- package/src/input/command-registry.ts +54 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/diff-runtime.ts +61 -30
- 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-content.ts +12 -0
- package/src/input/commands/session-workflow.ts +19 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/share-runtime.ts +9 -2
- 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-content-actions.ts +22 -8
- package/src/input/handler-feed-routes.ts +107 -7
- package/src/input/handler-feed.ts +49 -13
- package/src/input/handler-interactions.ts +3 -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 +3 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +10 -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 +41 -44
- package/src/panels/agent-inspector-shared.ts +35 -11
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +16 -62
- 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 +63 -14
- package/src/panels/diff-panel.ts +123 -60
- 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/git-panel.ts +9 -9
- package/src/panels/index.ts +7 -31
- package/src/panels/local-auth-panel.ts +10 -0
- 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/scrollable-list-panel.ts +9 -0
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/token-budget-panel.ts +5 -3
- package/src/panels/types.ts +26 -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/shell-surface.ts +8 -1
- package/src/renderer/turn-injection.ts +114 -0
- package/src/renderer/ui-factory.ts +91 -7
- package/src/runtime/bootstrap-command-context.ts +24 -1
- package/src/runtime/bootstrap-command-parts.ts +36 -7
- package/src/runtime/bootstrap-core.ts +19 -4
- package/src/runtime/bootstrap-hook-bridge.ts +5 -0
- package/src/runtime/bootstrap-shell.ts +41 -17
- 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 -233
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -609
- package/src/panels/docs-panel.ts +0 -375
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -664
- 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 -506
- 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 -557
- 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 -721
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -306
- package/src/panels/provider-health-views.ts +0 -560
- 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 -487
- 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 -530
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -465
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -656
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -427,6 +427,8 @@ export function handleCtrlC(
|
|
|
427
427
|
lastCtrlCTime: number,
|
|
428
428
|
setLastCtrlCTime: (value: number) => void,
|
|
429
429
|
setShowExitNotice: (value: boolean) => void,
|
|
430
|
+
lastCtrlCTimeoutId: ReturnType<typeof setTimeout> | null,
|
|
431
|
+
setLastCtrlCTimeoutId: (value: ReturnType<typeof setTimeout> | null) => void,
|
|
430
432
|
): void {
|
|
431
433
|
if (prompt.length > 0) {
|
|
432
434
|
saveUndoState();
|
|
@@ -436,17 +438,29 @@ export function handleCtrlC(
|
|
|
436
438
|
}
|
|
437
439
|
cancelGeneration?.();
|
|
438
440
|
const now = Date.now();
|
|
441
|
+
// Clear any hide-timer pending from a prior press before deciding this
|
|
442
|
+
// one's outcome, whichever branch below runs. Without this, a hide-timer
|
|
443
|
+
// scheduled by an earlier press could still fire after a newer notice
|
|
444
|
+
// window opened, or after exitApp() was just called (if exitApp isn't
|
|
445
|
+
// synchronous) — flipping showExitNotice/requestRender during a shutdown
|
|
446
|
+
// the user already believes is in progress.
|
|
447
|
+
if (lastCtrlCTimeoutId !== null) {
|
|
448
|
+
clearTimeout(lastCtrlCTimeoutId);
|
|
449
|
+
setLastCtrlCTimeoutId(null);
|
|
450
|
+
}
|
|
439
451
|
if (now - lastCtrlCTime < 1000) {
|
|
440
452
|
exitApp();
|
|
441
|
-
|
|
442
|
-
setLastCtrlCTime(now);
|
|
443
|
-
setShowExitNotice(true);
|
|
444
|
-
requestRender();
|
|
445
|
-
setTimeout(() => {
|
|
446
|
-
setShowExitNotice(false);
|
|
447
|
-
requestRender();
|
|
448
|
-
}, 1000);
|
|
453
|
+
return;
|
|
449
454
|
}
|
|
455
|
+
setLastCtrlCTime(now);
|
|
456
|
+
setShowExitNotice(true);
|
|
457
|
+
requestRender();
|
|
458
|
+
const timeoutId = setTimeout(() => {
|
|
459
|
+
setShowExitNotice(false);
|
|
460
|
+
setLastCtrlCTimeoutId(null);
|
|
461
|
+
requestRender();
|
|
462
|
+
}, 1000);
|
|
463
|
+
setLastCtrlCTimeoutId(timeoutId);
|
|
450
464
|
}
|
|
451
465
|
|
|
452
466
|
export function handleClipboardPaste(
|
|
@@ -30,6 +30,27 @@ export type PanelFocusRouteState = {
|
|
|
30
30
|
handlePathCompletion: () => boolean;
|
|
31
31
|
cyclePanelTab: (direction: 'next' | 'prev') => void;
|
|
32
32
|
onPanelInputConsumed?: (activePanel: import('../panels/types.ts').Panel | null, key: string) => void;
|
|
33
|
+
/**
|
|
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
|
|
49
|
+
* below.
|
|
50
|
+
*/
|
|
51
|
+
isTurnActive: () => boolean;
|
|
52
|
+
/** Cancel the active turn. Wired to commandContext.cancelGeneration. */
|
|
53
|
+
cancelGeneration: () => void;
|
|
33
54
|
};
|
|
34
55
|
|
|
35
56
|
export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputToken): {
|
|
@@ -66,6 +87,25 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
66
87
|
// (e.g. dismiss a confirm dialog or clear search). Only unfocus if the
|
|
67
88
|
// panel returns false (unconsumed) or there is no active panel.
|
|
68
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
|
+
}
|
|
69
109
|
const activePanel = state.panelManager.getActive();
|
|
70
110
|
const panelConsumedEscape = activePanel?.handleInput?.('escape') ?? false;
|
|
71
111
|
if (panelConsumedEscape) {
|
|
@@ -113,6 +153,20 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
113
153
|
|
|
114
154
|
if (token.type === 'text' && token.value) {
|
|
115
155
|
const activePanel = state.panelManager.getActive();
|
|
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');
|
|
167
|
+
state.requestRender();
|
|
168
|
+
return { handled: true, panelFocused };
|
|
169
|
+
}
|
|
116
170
|
if (activePanel?.handleInput) {
|
|
117
171
|
for (const ch of token.value) {
|
|
118
172
|
activePanel.handleInput(ch);
|
|
@@ -128,7 +182,8 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
128
182
|
export type IndicatorFocusRouteState = {
|
|
129
183
|
indicatorFocused: boolean;
|
|
130
184
|
modalOpened: (name: string) => void;
|
|
131
|
-
|
|
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;
|
|
132
187
|
requestRender: () => void;
|
|
133
188
|
};
|
|
134
189
|
|
|
@@ -149,8 +204,9 @@ export function handleIndicatorFocusToken(state: IndicatorFocusRouteState, token
|
|
|
149
204
|
}
|
|
150
205
|
if (token.logicalName === 'enter') {
|
|
151
206
|
indicatorFocused = false;
|
|
152
|
-
|
|
153
|
-
|
|
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();
|
|
154
210
|
state.requestRender();
|
|
155
211
|
return { handled: true, indicatorFocused };
|
|
156
212
|
}
|
|
@@ -229,7 +285,15 @@ export function handlePromptTextToken(state: TextRouteState, token: InputToken):
|
|
|
229
285
|
}
|
|
230
286
|
}
|
|
231
287
|
|
|
232
|
-
if (prompt === '/'
|
|
288
|
+
if (prompt === '/') {
|
|
289
|
+
// Arm commandMode as soon as the prompt becomes a bare '/', regardless of
|
|
290
|
+
// whether commandRegistry has been (re)attached yet — this is a one-shot
|
|
291
|
+
// transition (the only place commandMode ever becomes true), and gating
|
|
292
|
+
// it on commandRegistry meant a transient null during a modal/overlay
|
|
293
|
+
// handoff would permanently miss the window: every following keystroke
|
|
294
|
+
// is then processed as plain chat text with no way to recover. Dispatch
|
|
295
|
+
// (handleCommandModeToken, and the enter-key fallback below) still checks
|
|
296
|
+
// commandRegistry itself before actually running anything.
|
|
233
297
|
commandMode = true;
|
|
234
298
|
state.modalOpened('command');
|
|
235
299
|
state.autocomplete?.update('');
|
|
@@ -257,9 +321,18 @@ export type KeyRouteState = {
|
|
|
257
321
|
indicatorFocused: boolean;
|
|
258
322
|
conversationManager: ConversationManager | null;
|
|
259
323
|
commandContext: CommandContext | undefined;
|
|
324
|
+
/**
|
|
325
|
+
* Optional: only used by the enter-key desync safety net below (a stray
|
|
326
|
+
* slash-prefixed submission with commandMode somehow still false). When
|
|
327
|
+
* absent, that fallback simply doesn't trigger — the primary fix (arming
|
|
328
|
+
* commandMode on the '/' keystroke regardless of registry attachment, in
|
|
329
|
+
* handlePromptTextToken above) is what actually prevents the desync.
|
|
330
|
+
*/
|
|
331
|
+
commandRegistry?: CommandRegistry | null;
|
|
260
332
|
autocomplete: AutocompleteEngine | null;
|
|
261
333
|
blockActionsMenu: { open: (block: BlockMeta) => void };
|
|
262
|
-
|
|
334
|
+
/** W6.2 e: F2 opens+focuses the Fleet panel (which subsumes the retired process modal). */
|
|
335
|
+
openFleetPanel: () => void;
|
|
263
336
|
modalOpened: (name: string) => void;
|
|
264
337
|
saveUndoState: () => void;
|
|
265
338
|
/** Break the undo coalescing group (call on cursor moves). */
|
|
@@ -351,6 +424,30 @@ export function handlePromptKeyToken(state: KeyRouteState, token: InputToken): {
|
|
|
351
424
|
return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
|
|
352
425
|
}
|
|
353
426
|
if (text) {
|
|
427
|
+
// Safety net for a desynced commandMode: text is command-shaped (starts
|
|
428
|
+
// with '/') but commandMode never armed — e.g. the '/' keystroke landed
|
|
429
|
+
// during a modal/overlay handoff window. handleCommandModeToken (the
|
|
430
|
+
// normal dispatch path for '/name ...') never runs in this state since
|
|
431
|
+
// it early-returns when commandMode is false, so without this the text
|
|
432
|
+
// would fall straight through to submitInput as ordinary chat. Re-derive
|
|
433
|
+
// intent from the literal text instead of trusting commandMode alone.
|
|
434
|
+
if (!commandMode && text.startsWith('/') && state.commandRegistry && state.commandContext?.executeCommand) {
|
|
435
|
+
const parts = text.slice(1).trim().split(/\s+/);
|
|
436
|
+
const name = parts[0];
|
|
437
|
+
const args = parts.slice(1);
|
|
438
|
+
prompt = '';
|
|
439
|
+
cursorPos = 0;
|
|
440
|
+
if (name) {
|
|
441
|
+
const executeCommand = state.commandContext.executeCommand;
|
|
442
|
+
void executeCommand(name, args).then((handled) => {
|
|
443
|
+
if (!handled) {
|
|
444
|
+
state.commandContext?.submitInput?.(text);
|
|
445
|
+
}
|
|
446
|
+
state.requestRender();
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
|
|
450
|
+
}
|
|
354
451
|
const expanded = state.expandPrompt(text);
|
|
355
452
|
const historyRecallText = typeof expanded === 'string'
|
|
356
453
|
? expanded
|
|
@@ -508,8 +605,11 @@ export function handlePromptKeyToken(state: KeyRouteState, token: InputToken): {
|
|
|
508
605
|
|
|
509
606
|
if (token.logicalName === 'f2') {
|
|
510
607
|
indicatorFocused = false;
|
|
511
|
-
|
|
512
|
-
|
|
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();
|
|
513
613
|
return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
|
|
514
614
|
}
|
|
515
615
|
|
|
@@ -9,15 +9,13 @@ import type { SelectionResult } from './selection-modal.ts';
|
|
|
9
9
|
import { SearchManager } from './search.ts';
|
|
10
10
|
import type { InputHistory, HistorySearch } from './input-history.ts';
|
|
11
11
|
import type { BlockMeta, ConversationManager } from '../core/conversation';
|
|
12
|
-
import { ProcessModal } from '../renderer/process-modal.ts';
|
|
13
|
-
import { LiveTailModal } from '../renderer/live-tail-modal.ts';
|
|
14
12
|
import { BlockActionsMenu } from '../renderer/block-actions.ts';
|
|
15
|
-
import { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
|
|
16
13
|
import { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
17
14
|
import { BookmarkModal } from './bookmark-modal.ts';
|
|
18
15
|
import { SettingsModal } from './settings-modal.ts';
|
|
19
16
|
import type { McpWorkspace } from './mcp-workspace.ts';
|
|
20
17
|
import { SessionPickerModal } from './session-picker-modal.ts';
|
|
18
|
+
import type { ConfigModal } from './config-modal.ts';
|
|
21
19
|
import { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
22
20
|
import type { OnboardingWizardController } from './onboarding/onboarding-wizard.ts';
|
|
23
21
|
import type { OnboardingWizardAction } from './onboarding/onboarding-wizard.ts';
|
|
@@ -45,6 +43,7 @@ import type { PanelManager } from '../panels/panel-manager.ts';
|
|
|
45
43
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
46
44
|
import type { ModelPickerTarget } from './model-picker.ts';
|
|
47
45
|
import type { KillRing } from './kill-ring.ts';
|
|
46
|
+
import type { FocusTracker } from '../core/focus-tracker.ts';
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
49
|
* InputFeedContext — The single long-lived context object passed to feedInputTokens
|
|
@@ -72,8 +71,8 @@ import type { KillRing } from './kill-ring.ts';
|
|
|
72
71
|
* - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
|
|
73
72
|
* - `selectionModal`, `bookmarkModal`, `settingsModal`, `sessionPickerModal`,
|
|
74
73
|
* `profilePickerModal` — modal objects constructed once in InputHandler constructor
|
|
75
|
-
* - `filePicker`, `modelPicker`, `onboardingWizard`,
|
|
76
|
-
* `
|
|
74
|
+
* - `filePicker`, `modelPicker`, `onboardingWizard`,
|
|
75
|
+
* `contextInspectorModal`, `blockActionsMenu`, `searchManager`, `historySearch` —
|
|
77
76
|
* service objects constructed once
|
|
78
77
|
* - `panelManager`, `keybindingsManager` — from uiServices, stable for app lifetime
|
|
79
78
|
* - `modalStack` — reference to the handler's shared array (mutated in place)
|
|
@@ -111,6 +110,7 @@ export interface InputFeedContext {
|
|
|
111
110
|
readonly mcpWorkspace: McpWorkspace;
|
|
112
111
|
readonly sessionPickerModal: SessionPickerModal;
|
|
113
112
|
readonly profilePickerModal: ProfilePickerModal;
|
|
113
|
+
readonly configModal: ConfigModal;
|
|
114
114
|
readonly historySearch: HistorySearch;
|
|
115
115
|
commandRegistry: CommandRegistry | null;
|
|
116
116
|
commandContext: CommandContext | undefined;
|
|
@@ -118,9 +118,6 @@ export interface InputFeedContext {
|
|
|
118
118
|
readonly filePicker: FilePickerModal;
|
|
119
119
|
readonly modelPicker: ModelPickerModal;
|
|
120
120
|
readonly onboardingWizard: OnboardingWizardController;
|
|
121
|
-
readonly processModal: ProcessModal;
|
|
122
|
-
readonly liveTailModal: LiveTailModal;
|
|
123
|
-
readonly agentDetailModal: AgentDetailModal;
|
|
124
121
|
readonly contextInspectorModal: ContextInspectorModal;
|
|
125
122
|
readonly blockActionsMenu: BlockActionsMenu;
|
|
126
123
|
readonly searchManager: SearchManager;
|
|
@@ -131,6 +128,8 @@ export interface InputFeedContext {
|
|
|
131
128
|
inputHistory: InputHistory | null;
|
|
132
129
|
conversationManager: ConversationManager | null;
|
|
133
130
|
readonly killRing: KillRing;
|
|
131
|
+
/** Terminal focus tracker (W2.3) — updated here from 'focus' tokens, read by the unfocused-alert notifiers in core/. */
|
|
132
|
+
readonly focusTracker: FocusTracker;
|
|
134
133
|
readonly getHistory: () => InfiniteBuffer;
|
|
135
134
|
readonly getViewportHeight: () => number;
|
|
136
135
|
readonly getScrollTop: () => number;
|
|
@@ -180,7 +179,34 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
180
179
|
const lineCount = history.getLineCount();
|
|
181
180
|
const keybindings = context.keybindingsManager;
|
|
182
181
|
|
|
182
|
+
// Shared opener for the Fleet panel: makes it visible AND transfers keyboard
|
|
183
|
+
// focus to it. panelManager.open() only makes the panel active — focus is a
|
|
184
|
+
// separate axis (see PanelManager.focusPanels()/getFocusTarget()); without the
|
|
185
|
+
// focusPanels() call, j/k/i/K land silently in the composer until Tab. Used by
|
|
186
|
+
// the footer indicator's [Enter] and by F2 (W6.2 e: F2 and the footer
|
|
187
|
+
// indicator both subsume the retired process modal). Mirrors the Ctrl+P
|
|
188
|
+
// panel-picker launcher (ui-openers.ts openPanelPicker).
|
|
189
|
+
const openFleetPanel = (): void => {
|
|
190
|
+
context.panelManager.open('fleet');
|
|
191
|
+
context.panelManager.focusPanels();
|
|
192
|
+
context.panelFocused = true;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Paste-ness is a per-TOKEN property, computed at the handlePanelFocusToken
|
|
196
|
+
// call below — never a per-feed character sum. The SDK tokenizer emits a
|
|
197
|
+
// bracketed paste (\x1b[?2004h, enabled in main.ts terminal init) as ONE
|
|
198
|
+
// 'text' token holding the whole payload, while discrete keystrokes — even
|
|
199
|
+
// several batched into one feed() by render-tick latency — arrive as
|
|
200
|
+
// separate 1-char 'text' tokens. The old per-feed sum misread two quick nav
|
|
201
|
+
// keystrokes (e.g. j then k in one drain) as a "burst" and yanked focus.
|
|
183
202
|
for (const token of tokens) {
|
|
203
|
+
// Focus-reporting tokens (CSI ?1004h, W2.3) never reach the composer or any
|
|
204
|
+
// modal route — consumed here, first, unconditionally. No render needed.
|
|
205
|
+
if (token.type === 'focus') {
|
|
206
|
+
context.focusTracker.setFocused(token.action === 'in');
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
184
210
|
if (token.type === 'key' && context.keybindingsManager.matches('clear-cancel', token)) {
|
|
185
211
|
context.handleCtrlC();
|
|
186
212
|
continue;
|
|
@@ -200,6 +226,7 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
200
226
|
mcpWorkspace: context.mcpWorkspace,
|
|
201
227
|
sessionPickerModal: context.sessionPickerModal,
|
|
202
228
|
profilePickerModal: context.profilePickerModal,
|
|
229
|
+
configModal: context.configModal,
|
|
203
230
|
onboardingWizard: context.onboardingWizard,
|
|
204
231
|
helpOverlayActive: context.helpOverlayActive,
|
|
205
232
|
helpScrollOffset: context.helpScrollOffset,
|
|
@@ -214,9 +241,6 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
214
241
|
getViewportHeight: context.getViewportHeight,
|
|
215
242
|
requestRender: context.requestRender,
|
|
216
243
|
handleEscape: context.handleEscape,
|
|
217
|
-
liveTailModal: context.liveTailModal,
|
|
218
|
-
processModal: context.processModal,
|
|
219
|
-
agentDetailModal: context.agentDetailModal,
|
|
220
244
|
contextInspectorModal: context.contextInspectorModal,
|
|
221
245
|
modalOpened: context.modalOpened,
|
|
222
246
|
filePicker: context.filePicker,
|
|
@@ -320,6 +344,17 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
320
344
|
panelManager: context.panelManager,
|
|
321
345
|
keybindingsManager: context.keybindingsManager,
|
|
322
346
|
onPanelInputConsumed: context.onPanelInputConsumed,
|
|
347
|
+
// Per-token paste classification (Invariant B): a paste is a single
|
|
348
|
+
// 'text' token whose value holds more than one character.
|
|
349
|
+
isPasteToken: token.type === 'text' && token.value.length > 1,
|
|
350
|
+
// One-shot honesty hint when a paste is dropped into a non-capturing
|
|
351
|
+
// focused panel (Invariant A: focus never silently flips to the composer).
|
|
352
|
+
onPasteDropped: (panelName: string) =>
|
|
353
|
+
context.commandContext?.print(
|
|
354
|
+
`paste ignored — focus is on ${panelName}; Tab returns to composer`,
|
|
355
|
+
),
|
|
356
|
+
isTurnActive: () => context.commandContext?.isGenerating?.() ?? false,
|
|
357
|
+
cancelGeneration: () => context.commandContext?.cancelGeneration?.(),
|
|
323
358
|
}, token);
|
|
324
359
|
context.panelFocused = panelRoute.panelFocused;
|
|
325
360
|
if (panelRoute.handled) {
|
|
@@ -329,7 +364,7 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
329
364
|
const indicatorRoute = handleIndicatorFocusToken({
|
|
330
365
|
indicatorFocused: context.indicatorFocused,
|
|
331
366
|
modalOpened: context.modalOpened,
|
|
332
|
-
|
|
367
|
+
openFleetPanel,
|
|
333
368
|
requestRender: context.requestRender,
|
|
334
369
|
}, token);
|
|
335
370
|
context.indicatorFocused = indicatorRoute.indicatorFocused;
|
|
@@ -408,9 +443,10 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
408
443
|
indicatorFocused: context.indicatorFocused,
|
|
409
444
|
conversationManager: context.conversationManager,
|
|
410
445
|
commandContext: context.commandContext,
|
|
446
|
+
commandRegistry: context.commandRegistry,
|
|
411
447
|
autocomplete: context.autocomplete,
|
|
412
448
|
blockActionsMenu: { open: (block: BlockMeta) => context.blockActionsMenu.open(block) },
|
|
413
|
-
|
|
449
|
+
openFleetPanel,
|
|
414
450
|
modalOpened: context.modalOpened,
|
|
415
451
|
saveUndoState: context.saveUndoState,
|
|
416
452
|
breakUndoCoalesce: context.breakUndoCoalesce,
|
|
@@ -193,6 +193,8 @@ export function handleCtrlCForHandler(handler: InputHandler): void {
|
|
|
193
193
|
handler.lastCtrlCTime,
|
|
194
194
|
(value) => { handler.lastCtrlCTime = value; },
|
|
195
195
|
(value) => { handler.showExitNotice = value; },
|
|
196
|
+
handler.lastCtrlCTimeoutId,
|
|
197
|
+
(value) => { handler.lastCtrlCTimeoutId = value; },
|
|
196
198
|
);
|
|
197
199
|
}
|
|
198
200
|
|
|
@@ -223,14 +225,12 @@ export function handleEscapeForHandler(handler: InputHandler): void {
|
|
|
223
225
|
helpOverlayActive: handler.helpOverlayActive,
|
|
224
226
|
shortcutsOverlayActive: handler.shortcutsOverlayActive,
|
|
225
227
|
bookmarkModal: handler.bookmarkModal,
|
|
226
|
-
agentDetailModal: handler.agentDetailModal,
|
|
227
|
-
liveTailModal: handler.liveTailModal,
|
|
228
228
|
settingsModal: handler.settingsModal,
|
|
229
229
|
mcpWorkspace: handler.mcpWorkspace,
|
|
230
230
|
sessionPickerModal: handler.sessionPickerModal,
|
|
231
231
|
profilePickerModal: handler.profilePickerModal,
|
|
232
|
+
configModal: handler.configModal,
|
|
232
233
|
contextInspectorModal: handler.contextInspectorModal,
|
|
233
|
-
processModal: handler.processModal,
|
|
234
234
|
modelPicker: handler.modelPicker,
|
|
235
235
|
filePicker: handler.filePicker,
|
|
236
236
|
blockActionsMenu: handler.blockActionsMenu,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
|
|
2
2
|
import type { SelectionResult, SelectionAction } from './selection-modal.ts';
|
|
3
3
|
import type { CommandContext } from './command-registry.ts';
|
|
4
|
+
import type { ConfigModal } from './config-modal.ts';
|
|
4
5
|
import { openTtsProviderPicker, openTtsVoicePicker } from './tts-settings-actions.ts';
|
|
5
6
|
import { isTextBackspace } from './delete-key-policy.ts';
|
|
6
7
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
@@ -533,3 +534,67 @@ export function handleProfilePickerToken(state: ProfilePickerRouteState, token:
|
|
|
533
534
|
state.requestRender();
|
|
534
535
|
return true;
|
|
535
536
|
}
|
|
537
|
+
|
|
538
|
+
type ConfigModalRouteState = {
|
|
539
|
+
configModal: ConfigModal;
|
|
540
|
+
commandContext?: CommandContext;
|
|
541
|
+
requestRender: () => void;
|
|
542
|
+
handleEscape: () => void;
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Route a key to the generic config-modal host (W6.1 MIGRATE-TO-MODAL
|
|
547
|
+
* surfaces). The host owns the reserved navigation keys (Esc, up/down/j/k tab
|
|
548
|
+
* left/right); every other key is offered to the active surface's declarative
|
|
549
|
+
* action table (fireAction handles the two-press confirm for destructive
|
|
550
|
+
* actions). Any unrecognised key is absorbed so the modal stays modal — this is
|
|
551
|
+
* the same "active modal captures all keys" shape as the other modal routers.
|
|
552
|
+
*/
|
|
553
|
+
export function handleConfigModalToken(state: ConfigModalRouteState, token: InputToken): boolean {
|
|
554
|
+
if (!state.configModal.active) return false;
|
|
555
|
+
|
|
556
|
+
if (token.type === 'key') {
|
|
557
|
+
switch (token.logicalName) {
|
|
558
|
+
case 'escape':
|
|
559
|
+
state.handleEscape();
|
|
560
|
+
return true;
|
|
561
|
+
case 'up':
|
|
562
|
+
state.configModal.moveUp();
|
|
563
|
+
state.requestRender();
|
|
564
|
+
return true;
|
|
565
|
+
case 'down':
|
|
566
|
+
state.configModal.moveDown();
|
|
567
|
+
state.requestRender();
|
|
568
|
+
return true;
|
|
569
|
+
case 'left':
|
|
570
|
+
state.configModal.prevTab();
|
|
571
|
+
state.requestRender();
|
|
572
|
+
return true;
|
|
573
|
+
case 'right':
|
|
574
|
+
case 'tab':
|
|
575
|
+
state.configModal.nextTab();
|
|
576
|
+
state.requestRender();
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
} else if (token.type === 'text') {
|
|
580
|
+
if (token.value === 'j') { state.configModal.moveDown(); state.requestRender(); return true; }
|
|
581
|
+
if (token.value === 'k') { state.configModal.moveUp(); state.requestRender(); return true; }
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const actionKey = token.type === 'key' ? (token.logicalName ?? '') : token.type === 'text' ? token.value : '';
|
|
585
|
+
const fired = actionKey.length > 0 && state.configModal.fireAction(actionKey, {
|
|
586
|
+
print: (message: string) => state.commandContext?.print(message),
|
|
587
|
+
executeCommand: state.commandContext?.executeCommand,
|
|
588
|
+
openModal: state.commandContext?.openModal,
|
|
589
|
+
});
|
|
590
|
+
if (fired) {
|
|
591
|
+
state.requestRender();
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Unhandled key while a config modal is open: drop any pending confirm and
|
|
596
|
+
// absorb the key (the modal owns the keyboard while it is active).
|
|
597
|
+
state.configModal.clearConfirmOnMiss();
|
|
598
|
+
state.requestRender();
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
@@ -40,12 +40,12 @@ export type EscapeState = ModalStackState & {
|
|
|
40
40
|
selectionCallback: ((result: SelectionResult | null) => void) | null;
|
|
41
41
|
bookmarkModal: ModalStackState['bookmarkModal'] & { open: () => void };
|
|
42
42
|
contextInspectorModal: ModalStackState['contextInspectorModal'] & { open: () => void };
|
|
43
|
-
processModal: ModalStackState['processModal'] & { open: () => void };
|
|
44
43
|
settingsModal: ModalStackState['settingsModal'] & {
|
|
45
44
|
editingMode: boolean;
|
|
46
45
|
cancelEdit: () => void;
|
|
47
46
|
};
|
|
48
47
|
mcpWorkspace?: ModalStackState['mcpWorkspace'];
|
|
48
|
+
configModal: ModalStackState['configModal'] & { reopen: () => void };
|
|
49
49
|
selectionModal: ModalStackState['selectionModal'];
|
|
50
50
|
autocompleteReset: () => void;
|
|
51
51
|
autocompleteUpdate?: (query: string) => void;
|
|
@@ -120,14 +120,12 @@ export function handleEscape(state: EscapeState): {
|
|
|
120
120
|
shortcutsScrollOffset = 0;
|
|
121
121
|
},
|
|
122
122
|
closeBookmark: () => state.bookmarkModal.close(),
|
|
123
|
-
closeAgentDetail: () => state.agentDetailModal.close(),
|
|
124
|
-
closeLiveTail: () => state.liveTailModal.close(),
|
|
125
123
|
closeSettings: () => state.settingsModal.close(),
|
|
126
124
|
closeMcpWorkspace: () => state.mcpWorkspace?.close(),
|
|
127
125
|
closeSessionPicker: () => state.sessionPickerModal.close(),
|
|
128
126
|
closeProfilePicker: () => state.profilePickerModal.close(),
|
|
127
|
+
closeConfigModal: () => state.configModal.close(),
|
|
129
128
|
closeContextInspector: () => state.contextInspectorModal.close(),
|
|
130
|
-
closeProcess: () => state.processModal.close(),
|
|
131
129
|
closeModelPicker: () => {
|
|
132
130
|
state.modelPicker.close();
|
|
133
131
|
state.restoreOnboardingModelPickerCancelState?.();
|
|
@@ -161,8 +159,8 @@ export function handleEscape(state: EscapeState): {
|
|
|
161
159
|
openHelp: () => { helpOverlayActive = true; },
|
|
162
160
|
openShortcuts: () => { shortcutsOverlayActive = true; },
|
|
163
161
|
openBookmark: () => state.bookmarkModal.open(),
|
|
164
|
-
openProcess: () => state.processModal.open(),
|
|
165
162
|
openContextInspector: () => state.contextInspectorModal.open(),
|
|
163
|
+
openConfigModal: () => state.configModal.reopen(),
|
|
166
164
|
openMcpWorkspace: () => state.mcpWorkspace?.reopen(),
|
|
167
165
|
openOnboarding: () => state.onboardingWizard?.reopen(),
|
|
168
166
|
openCommandMode: () => {
|
|
@@ -5,13 +5,11 @@ import type { BookmarkModal } from './bookmark-modal.ts';
|
|
|
5
5
|
import type { SettingsModal } from './settings-modal.ts';
|
|
6
6
|
import type { SessionPickerModal } from './session-picker-modal.ts';
|
|
7
7
|
import type { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
8
|
+
import type { ConfigModal } from './config-modal.ts';
|
|
8
9
|
import type { HistorySearch } from './input-history.ts';
|
|
9
10
|
import type { ModelPickerModal } from './model-picker.ts';
|
|
10
11
|
import { handleMcpWorkspaceToken, type McpWorkspace } from './mcp-workspace.ts';
|
|
11
12
|
import type { CommandContext } from './command-registry.ts';
|
|
12
|
-
import type { LiveTailModal } from '../renderer/live-tail-modal.ts';
|
|
13
|
-
import type { ProcessModal } from '../renderer/process-modal.ts';
|
|
14
|
-
import type { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
|
|
15
13
|
import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
16
14
|
import type { FilePickerModal } from './file-picker.ts';
|
|
17
15
|
import type { BlockActionsMenu, BlockActionId } from '../renderer/block-actions.ts';
|
|
@@ -21,6 +19,7 @@ import { handleHistorySearchToken, handleOverlayToken, handleSearchModeToken } f
|
|
|
21
19
|
import { handleOnboardingWizardToken } from './onboarding/handler-onboarding-routes.ts';
|
|
22
20
|
import {
|
|
23
21
|
handleBookmarkModalToken,
|
|
22
|
+
handleConfigModalToken,
|
|
24
23
|
handleProfilePickerToken,
|
|
25
24
|
handleSelectionModalToken,
|
|
26
25
|
handleSessionPickerToken,
|
|
@@ -30,9 +29,7 @@ import {
|
|
|
30
29
|
handleBlockActionsToken,
|
|
31
30
|
handleEscapeOnlyModalToken,
|
|
32
31
|
handleFilePickerToken,
|
|
33
|
-
handleLiveTailToken,
|
|
34
32
|
handleModelPickerToken,
|
|
35
|
-
handleProcessModalToken,
|
|
36
33
|
} from './handler-picker-routes.ts';
|
|
37
34
|
|
|
38
35
|
export type ModalTokenRouteState = {
|
|
@@ -47,6 +44,7 @@ export type ModalTokenRouteState = {
|
|
|
47
44
|
mcpWorkspace: McpWorkspace;
|
|
48
45
|
sessionPickerModal: SessionPickerModal;
|
|
49
46
|
profilePickerModal: ProfilePickerModal;
|
|
47
|
+
configModal: ConfigModal;
|
|
50
48
|
onboardingWizard: OnboardingWizardController;
|
|
51
49
|
helpOverlayActive: boolean;
|
|
52
50
|
helpScrollOffset: number;
|
|
@@ -61,9 +59,6 @@ export type ModalTokenRouteState = {
|
|
|
61
59
|
getViewportHeight: () => number;
|
|
62
60
|
requestRender: () => void;
|
|
63
61
|
handleEscape: () => void;
|
|
64
|
-
liveTailModal: LiveTailModal;
|
|
65
|
-
processModal: ProcessModal;
|
|
66
|
-
agentDetailModal: AgentDetailModal;
|
|
67
62
|
contextInspectorModal: ContextInspectorModal;
|
|
68
63
|
modalOpened: (name: string) => void;
|
|
69
64
|
filePicker: FilePickerModal;
|
|
@@ -177,6 +172,15 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
177
172
|
return withState(state, true);
|
|
178
173
|
}
|
|
179
174
|
|
|
175
|
+
if (handleConfigModalToken({
|
|
176
|
+
configModal: state.configModal,
|
|
177
|
+
commandContext: state.commandContext,
|
|
178
|
+
requestRender: state.requestRender,
|
|
179
|
+
handleEscape: state.handleEscape,
|
|
180
|
+
}, token)) {
|
|
181
|
+
return withState(state, true);
|
|
182
|
+
}
|
|
183
|
+
|
|
180
184
|
const overlayState = {
|
|
181
185
|
helpOverlayActive: state.helpOverlayActive,
|
|
182
186
|
helpScrollOffset: state.helpScrollOffset,
|
|
@@ -223,36 +227,10 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
223
227
|
return withState(state, true);
|
|
224
228
|
}
|
|
225
229
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
handleEscape: state.handleEscape,
|
|
231
|
-
}, token)) {
|
|
232
|
-
return withState(state, true);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Agent detail modal: route c + confirm keys before escape-close.
|
|
236
|
-
// handleKey() consumes confirm-flow keys (y, Enter, n, Esc) and the 'c'
|
|
237
|
-
// initiator; unhandled keys (including Esc when no confirm is pending)
|
|
238
|
-
// fall through to escape-close below.
|
|
239
|
-
if (state.agentDetailModal.active) {
|
|
240
|
-
const keyStr: string =
|
|
241
|
-
token.type === 'key' ? (token.logicalName ?? '') :
|
|
242
|
-
token.type === 'text' ? token.value : '';
|
|
243
|
-
if (keyStr && state.agentDetailModal.handleKey(keyStr)) {
|
|
244
|
-
state.requestRender();
|
|
245
|
-
return withState(state, true);
|
|
246
|
-
}
|
|
247
|
-
// 'c' was not consumed (non-cancellable), or any other key.
|
|
248
|
-
// Esc closes the modal; all other keys are absorbed by the active modal.
|
|
249
|
-
if (token.type === 'key' && token.logicalName === 'escape') {
|
|
250
|
-
state.handleEscape();
|
|
251
|
-
return withState(state, true);
|
|
252
|
-
}
|
|
253
|
-
state.requestRender();
|
|
254
|
-
return withState(state, true);
|
|
255
|
-
}
|
|
230
|
+
// W6.1 retirement: the live-tail, agent-detail, and process-modal token
|
|
231
|
+
// routes were removed with those modals (F2 now opens Fleet, which subsumes
|
|
232
|
+
// the live process tree; the three modals were structurally unreachable after
|
|
233
|
+
// the F2 repoint and were deleted).
|
|
256
234
|
|
|
257
235
|
if (handleEscapeOnlyModalToken({
|
|
258
236
|
active: state.contextInspectorModal.active,
|
|
@@ -262,17 +240,6 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
262
240
|
return withState(state, true);
|
|
263
241
|
}
|
|
264
242
|
|
|
265
|
-
if (handleProcessModalToken({
|
|
266
|
-
processModal: state.processModal,
|
|
267
|
-
liveTailModal: state.liveTailModal,
|
|
268
|
-
agentDetailModal: state.agentDetailModal,
|
|
269
|
-
modalOpened: state.modalOpened,
|
|
270
|
-
requestRender: state.requestRender,
|
|
271
|
-
handleEscape: state.handleEscape,
|
|
272
|
-
}, token)) {
|
|
273
|
-
return withState(state, true);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
243
|
const filePickerState = {
|
|
277
244
|
filePicker: state.filePicker,
|
|
278
245
|
prompt: state.prompt,
|