@pellux/goodvibes-tui 1.1.0 → 1.9.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.
Files changed (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -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.then((handled) => {
96
- if (handled) {
97
- state.requestRender();
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
  }
@@ -133,9 +143,16 @@ function withPanelFocusSync(context: CommandContext, state: CommandModeRouteStat
133
143
  }
134
144
  : undefined,
135
145
  showPanel: context.showPanel
136
- ? (panelId, pane) => {
137
- context.showPanel?.(panelId, pane);
138
- state.panelFocused = true;
146
+ ? (panelId, pane, target, opts) => {
147
+ context.showPanel?.(panelId, pane, target, opts);
148
+ // UX-C: showPanel no longer transfers focus by default — the command
149
+ // path leaves the composer focused (ui-openers.ts's showPanel only
150
+ // calls focusPanels() when opts.focus is set). Only flip the local
151
+ // panelFocused mirror when the caller explicitly asked for focus;
152
+ // otherwise leave it exactly as it was (the composer, since you can
153
+ // only type a command from there in the first place). `target` is the
154
+ // DEBT-5 deep-link jump target, forwarded verbatim.
155
+ if (opts?.focus) state.panelFocused = true;
139
156
  }
140
157
  : undefined,
141
158
  focusPanels: context.focusPanels
@@ -421,7 +421,7 @@ export function handleCtrlC(
421
421
  saveUndoState: () => void,
422
422
  setPrompt: (value: string) => void,
423
423
  setCursorPos: (value: number) => void,
424
- cancelGeneration: (() => void) | undefined,
424
+ cancelGeneration: (() => boolean | void) | undefined,
425
425
  exitApp: () => void,
426
426
  requestRender: () => void,
427
427
  lastCtrlCTime: number,
@@ -436,7 +436,22 @@ export function handleCtrlC(
436
436
  setCursorPos(0);
437
437
  return;
438
438
  }
439
- cancelGeneration?.();
439
+ // Stopping in-flight speech / aborting the turn must never prevent the quit
440
+ // double-press window from advancing. A throw from the async TTS-stop path
441
+ // (e.g. killing the audio subprocess) would otherwise swallow this press and
442
+ // strand the user unable to quit while TTS is speaking. Decouple the two: the
443
+ // press below is recorded no matter how speech-stop fares. (UX-B item 6b.)
444
+ let stoppedSpeech = false;
445
+ try {
446
+ stoppedSpeech = cancelGeneration?.() === true;
447
+ } catch {
448
+ // Non-fatal to the exit chord; the quit-window bookkeeping still runs.
449
+ }
450
+ // A press that silenced LIVE speech is consumed by that job (batch replay
451
+ // D5) — symmetric with the prompt-clearing press above. The quit chord
452
+ // ("Ctrl+C x2") starts from a quiet state; turn-aborts still count toward
453
+ // the double-press.
454
+ if (stoppedSpeech) return;
440
455
  const now = Date.now();
441
456
  // Clear any hide-timer pending from a prior press before deciding this
442
457
  // one's outcome, whichever branch below runs. Without this, a hide-timer
@@ -13,11 +13,13 @@ import {
13
13
  } from './handler-prompt-buffer.ts';
14
14
  import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, registerPaste } from './handler-content-actions.ts';
15
15
  import type { PanelManager } from '../panels/panel-manager.ts';
16
- import { renderPanelWorkspaceBar } from '../renderer/panel-workspace-bar.ts';
17
- import type { TabHitRegion } from '../renderer/tab-strip.ts';
16
+ import { getPanelUnderMouse, workspaceTabAtMouse } from './panel-mouse-geometry.ts';
17
+ import type { PanelMouseLayout } from './panel-mouse-geometry.ts';
18
+ export type { PanelMouseLayout };
18
19
  import type { KeybindingsManager } from './keybindings.ts';
19
20
  import type { KillRing } from './kill-ring.ts';
20
21
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
22
+ import { trackPanelPasteFloodGuard, type PanelBurstGuardState } from './panel-paste-flood-guard.ts';
21
23
 
22
24
  export type PanelFocusRouteState = {
23
25
  panelManager: PanelManager;
@@ -31,14 +33,29 @@ export type PanelFocusRouteState = {
31
33
  cyclePanelTab: (direction: 'next' | 'prev') => void;
32
34
  onPanelInputConsumed?: (activePanel: import('../panels/types.ts').Panel | null, key: string) => void;
33
35
  /**
34
- * True when the tokens produced by the current feed() call carry more than
35
- * one printable character total (a bracketed paste, or several 1-char text
36
- * tokens from a fast-typed burst in one stdin chunk). A burst can never be
37
- * a deliberate single-key panel hotkey, so it must not be exploded into
38
- * per-char activePanel.handleInput(ch) calls — see the text-token branch
36
+ * True when THIS token is a paste: a single 'text' token whose value holds
37
+ * more than one character. The SDK tokenizer emits a bracketed paste
38
+ * (\x1b[?2004h, enabled in main.ts init) as exactly one such token; discrete
39
+ * keystrokes even several batched into one feed() by render-tick latency
40
+ * arrive as separate 1-char tokens. See the text-token branch below.
41
+ */
42
+ isPasteToken: boolean;
43
+ /**
44
+ * One-shot hint emitted when a paste is dropped because the focused panel has
45
+ * no text capture (Invariant A: focus must not silently flip to the composer).
46
+ */
47
+ onPasteDropped?: (panelName: string) => void;
48
+ /** Wall-clock time (ms) for this token; `burstGuard` is DEBT-5 item 5's flood-guard state, mutated in place across tokens (see panel-paste-flood-guard.ts). */
49
+ now: number;
50
+ burstGuard: PanelBurstGuardState;
51
+ /**
52
+ * True while a turn is actively streaming; gates Escape's cancel-first
53
+ * precedence (I6.1) ahead of the panel's own two-stage escape contract
39
54
  * below.
40
55
  */
41
- isPrintableBurst: boolean;
56
+ isTurnActive: () => boolean;
57
+ /** Cancel the active turn. Wired to commandContext.cancelGeneration. */
58
+ cancelGeneration: () => void;
42
59
  };
43
60
 
44
61
  export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputToken): {
@@ -75,6 +92,18 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
75
92
  // (e.g. dismiss a confirm dialog or clear search). Only unfocus if the
76
93
  // panel returns false (unconsumed) or there is no active panel.
77
94
  if (token.logicalName === 'escape') {
95
+ // I6.1/replay R5: while streaming, a focused panel must not swallow the
96
+ // only way to cancel — Escape's first job is always cancel-turn; the
97
+ // panel's own two-stage consume-or-unfocus contract only runs once no
98
+ // turn is active, so a *second* Escape falls through to it normally
99
+ // (panelFocused stays unchanged here). Calls cancelGeneration()
100
+ // directly rather than the shared handleEscape() (handler-modal-stack.ts),
101
+ // whose fallthrough order doesn't guarantee reaching it first.
102
+ if (state.isTurnActive()) {
103
+ state.cancelGeneration();
104
+ state.requestRender();
105
+ return { handled: true, panelFocused };
106
+ }
78
107
  const activePanel = state.panelManager.getActive();
79
108
  const panelConsumedEscape = activePanel?.handleInput?.('escape') ?? false;
80
109
  if (panelConsumedEscape) {
@@ -88,13 +117,10 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
88
117
  }
89
118
  const kb = state.keybindingsManager;
90
119
  // NOTE: panel-tab-next/prev, panel-close, and panel-close-all are handled
91
- // globally in handleGlobalShortcutToken, which runs earlier in the feed
92
- // loop and consumes those tokens before they ever reach this focused-panel
93
- // route. Their old copies lived here too and had drifted (this route never
94
- // called pm.hide() on close-all); they were unreachable, so they are gone
95
- // and close/close-all semantics now live in exactly one place. Only
96
- // panel-focus-toggle stays here, because it is meaningful only while the
97
- // panel workspace already owns focus (it swaps between the two panes).
120
+ // globally in handleGlobalShortcutToken (runs earlier in the feed loop) —
121
+ // their old, drifted copies here were unreachable and are gone. Only
122
+ // panel-focus-toggle stays, since it's meaningful only once the panel
123
+ // workspace already owns focus (it swaps between the two panes).
98
124
  if (kb.matches('panel-focus-toggle', token)) {
99
125
  // Switch keyboard focus between the top and bottom panes (no-op when
100
126
  // there is no visible, non-empty bottom pane).
@@ -122,19 +148,32 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
122
148
 
123
149
  if (token.type === 'text' && token.value) {
124
150
  const activePanel = state.panelManager.getActive();
125
- // A burst (paste or several fast-typed chars in one stdin chunk) is never
126
- // a deliberate single-key panel hotkey. Eating it silently as per-char
127
- // hotkeys leaves the user with no echo and no error, looking exactly
128
- // like dead keystrokes. Unless the active panel has its own text-capture
129
- // buffer open (a `/`-search or draft-answer field that deliberately
130
- // wants every character, burst or not — see isCapturingTextBurst), unfocus
131
- // the panel and let the token fall through to the prompt route instead,
132
- // same as it would from an unfocused panel.
133
- if (state.isPrintableBurst && !activePanel?.isCapturingTextBurst?.()) {
151
+ // UX-C: '/' is an explicit transfer verb back to the composer (a capturing panel, isCapturingTextBurst, keeps '/' for itself).
152
+ if (token.value === '/' && !activePanel?.isCapturingTextBurst?.()) {
134
153
  panelFocused = false;
135
- state.requestRender();
136
154
  return { handled: false, panelFocused };
137
155
  }
156
+ // Invariant A/B (W6.2): a paste (isPasteToken, one multi-char token) into
157
+ // a capturing panel forwards verbatim below; elsewhere it is DROPPED with
158
+ // a one-shot hint (never exploded into hotkeys, never a silent focus
159
+ // flip — focus moves only on an explicit verb). Discrete keystrokes fall
160
+ // through to the per-char dispatch below.
161
+ if (state.isPasteToken && !activePanel?.isCapturingTextBurst?.()) {
162
+ state.onPasteDropped?.(activePanel?.name ?? 'the panel');
163
+ state.requestRender();
164
+ return { handled: true, panelFocused };
165
+ }
166
+
167
+ // DEBT-5 item 5 — paste flood guard (rate-based; see panel-paste-flood-guard.ts).
168
+ if (!state.isPasteToken && !activePanel?.isCapturingTextBurst?.()) {
169
+ const guard = trackPanelPasteFloodGuard(state.burstGuard, state.now);
170
+ if (!guard.dispatch) {
171
+ if (guard.showHintNow) state.onPasteDropped?.(activePanel?.name ?? 'the panel');
172
+ state.requestRender();
173
+ return { handled: true, panelFocused };
174
+ }
175
+ }
176
+
138
177
  if (activePanel?.handleInput) {
139
178
  for (const ch of token.value) {
140
179
  activePanel.handleInput(ch);
@@ -150,7 +189,8 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
150
189
  export type IndicatorFocusRouteState = {
151
190
  indicatorFocused: boolean;
152
191
  modalOpened: (name: string) => void;
153
- processModal: { open: () => void };
192
+ /** 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). */
193
+ openFleetPanel: () => void;
154
194
  requestRender: () => void;
155
195
  };
156
196
 
@@ -171,8 +211,8 @@ export function handleIndicatorFocusToken(state: IndicatorFocusRouteState, token
171
211
  }
172
212
  if (token.logicalName === 'enter') {
173
213
  indicatorFocused = false;
174
- state.modalOpened('process');
175
- state.processModal.open();
214
+ // W2.2: opens the Fleet panel (see openFleetPanel's own doc, line ~352).
215
+ state.openFleetPanel();
176
216
  state.requestRender();
177
217
  return { handled: true, indicatorFocused };
178
218
  }
@@ -297,7 +337,8 @@ export type KeyRouteState = {
297
337
  commandRegistry?: CommandRegistry | null;
298
338
  autocomplete: AutocompleteEngine | null;
299
339
  blockActionsMenu: { open: (block: BlockMeta) => void };
300
- processModal: { open: () => void };
340
+ /** W6.2 e: F2 opens+focuses the Fleet panel (which subsumes the retired process modal). */
341
+ openFleetPanel: () => void;
301
342
  modalOpened: (name: string) => void;
302
343
  saveUndoState: () => void;
303
344
  /** Break the undo coalescing group (call on cursor moves). */
@@ -568,10 +609,12 @@ export function handlePromptKeyToken(state: KeyRouteState, token: InputToken): {
568
609
  return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
569
610
  }
570
611
 
612
+ // UX-C: F2 is now handled globally with toggle semantics (handler-shortcuts.ts)
613
+ // BEFORE it ever reaches this route; this branch is dead in the real
614
+ // pipeline but stays for panel-entry-points-reachable.test.ts's direct test.
571
615
  if (token.logicalName === 'f2') {
572
616
  indicatorFocused = false;
573
- state.modalOpened('process');
574
- state.processModal.open();
617
+ state.openFleetPanel();
575
618
  return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
576
619
  }
577
620
 
@@ -594,89 +637,6 @@ export type MouseRouteState = {
594
637
  handleCopy: () => void;
595
638
  };
596
639
 
597
- export type PanelMouseLayout = {
598
- x: number;
599
- y: number;
600
- width: number;
601
- height: number;
602
- hasBottomPane: boolean;
603
- verticalSplitRatio: number;
604
- };
605
-
606
- function clampRatio(value: number): number {
607
- return Math.max(0.2, Math.min(0.8, value));
608
- }
609
-
610
- function getActivePanelInPane(panelManager: PanelManager, pane: 'top' | 'bottom') {
611
- const target = pane === 'top' ? panelManager.getTopPane() : panelManager.getBottomPane();
612
- return target.panels[target.activeIndex] ?? null;
613
- }
614
-
615
- function getPanelUnderMouse(
616
- panelManager: PanelManager,
617
- layout: PanelMouseLayout | null,
618
- row: number,
619
- col: number,
620
- ) {
621
- if (
622
- layout === null
623
- || !panelManager.isVisible()
624
- || panelManager.getAllOpen().length === 0
625
- || col < layout.x
626
- || col >= layout.x + layout.width
627
- || row < layout.y
628
- || row >= layout.y + layout.height
629
- ) {
630
- return null;
631
- }
632
-
633
- const panelRow = row - layout.y;
634
- if (!layout.hasBottomPane) {
635
- return getActivePanelInPane(panelManager, 'top');
636
- }
637
-
638
- // Single consolidated workspace bar (row 0) + h-separator; the rest splits
639
- // between the two panes' content.
640
- const panelAreaRows = Math.max(0, layout.height - 1);
641
- const contentRows = Math.max(0, panelAreaRows - 1);
642
- const topContentRows = contentRows <= 1
643
- ? contentRows
644
- : Math.max(1, Math.floor(contentRows * clampRatio(layout.verticalSplitRatio)));
645
- // panelRow 0 = workspace bar; rows 1..topContentRows = top pane; rest = bottom.
646
- return panelRow <= topContentRows
647
- ? getActivePanelInPane(panelManager, 'top')
648
- : getActivePanelInPane(panelManager, 'bottom');
649
- }
650
-
651
- /**
652
- * If the mouse is over the consolidated workspace tab bar (the first panel
653
- * row), return the index of the tab under the cursor, else null. Recomputes the
654
- * tab hit regions by rendering the bar with a layout callback — cheap and keeps
655
- * the click geometry in lockstep with what was drawn.
656
- */
657
- function workspaceTabAtMouse(
658
- panelManager: PanelManager,
659
- layout: PanelMouseLayout | null,
660
- row: number,
661
- col: number,
662
- ): number | null {
663
- if (
664
- layout === null
665
- || !panelManager.isVisible()
666
- || panelManager.getAllOpen().length === 0
667
- || row !== layout.y // workspace bar is the first panel row
668
- || col < layout.x
669
- || col >= layout.x + layout.width
670
- ) {
671
- return null;
672
- }
673
- let regions: readonly TabHitRegion[] = [];
674
- renderPanelWorkspaceBar(panelManager.getWorkspaceTabs(), layout.width, true, (r) => { regions = r; });
675
- const relCol = col - layout.x;
676
- const hit = regions.find((rg) => relCol >= rg.startCol && relCol < rg.endCol);
677
- return hit ? hit.index : null;
678
- }
679
-
680
640
  function scrollPanelUnderMouse(
681
641
  state: MouseRouteState,
682
642
  token: Extract<InputToken, { type: 'mouse' }>,
@@ -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';
@@ -34,6 +32,7 @@ import {
34
32
  handlePromptTextToken,
35
33
  type PanelMouseLayout,
36
34
  } from './handler-feed-routes.ts';
35
+ import type { PanelBurstGuardState } from './panel-paste-flood-guard.ts';
37
36
  import type { WrappedPromptInfo } from './handler-prompt-buffer.ts';
38
37
  import { handleModalTokenRoutes } from './handler-modal-token-routes.ts';
39
38
  import { handleCommandModeToken } from './handler-command-route.ts';
@@ -45,6 +44,7 @@ import type { PanelManager } from '../panels/panel-manager.ts';
45
44
  import type { KeybindingsManager } from './keybindings.ts';
46
45
  import type { ModelPickerTarget } from './model-picker.ts';
47
46
  import type { KillRing } from './kill-ring.ts';
47
+ import type { FocusTracker } from '../core/focus-tracker.ts';
48
48
 
49
49
  /**
50
50
  * InputFeedContext — The single long-lived context object passed to feedInputTokens
@@ -72,8 +72,8 @@ import type { KillRing } from './kill-ring.ts';
72
72
  * - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
73
73
  * - `selectionModal`, `bookmarkModal`, `settingsModal`, `sessionPickerModal`,
74
74
  * `profilePickerModal` — modal objects constructed once in InputHandler constructor
75
- * - `filePicker`, `modelPicker`, `onboardingWizard`, `processModal`, `liveTailModal`,
76
- * `agentDetailModal`, `contextInspectorModal`, `blockActionsMenu`, `searchManager`, `historySearch` —
75
+ * - `filePicker`, `modelPicker`, `onboardingWizard`,
76
+ * `contextInspectorModal`, `blockActionsMenu`, `searchManager`, `historySearch` —
77
77
  * service objects constructed once
78
78
  * - `panelManager`, `keybindingsManager` — from uiServices, stable for app lifetime
79
79
  * - `modalStack` — reference to the handler's shared array (mutated in place)
@@ -111,6 +111,7 @@ export interface InputFeedContext {
111
111
  readonly mcpWorkspace: McpWorkspace;
112
112
  readonly sessionPickerModal: SessionPickerModal;
113
113
  readonly profilePickerModal: ProfilePickerModal;
114
+ readonly configModal: ConfigModal;
114
115
  readonly historySearch: HistorySearch;
115
116
  commandRegistry: CommandRegistry | null;
116
117
  commandContext: CommandContext | undefined;
@@ -118,9 +119,6 @@ export interface InputFeedContext {
118
119
  readonly filePicker: FilePickerModal;
119
120
  readonly modelPicker: ModelPickerModal;
120
121
  readonly onboardingWizard: OnboardingWizardController;
121
- readonly processModal: ProcessModal;
122
- readonly liveTailModal: LiveTailModal;
123
- readonly agentDetailModal: AgentDetailModal;
124
122
  readonly contextInspectorModal: ContextInspectorModal;
125
123
  readonly blockActionsMenu: BlockActionsMenu;
126
124
  readonly searchManager: SearchManager;
@@ -131,6 +129,10 @@ export interface InputFeedContext {
131
129
  inputHistory: InputHistory | null;
132
130
  conversationManager: ConversationManager | null;
133
131
  readonly killRing: KillRing;
132
+ /** Terminal focus tracker (W2.3) — updated here from 'focus' tokens, read by the unfocused-alert notifiers in core/. */
133
+ readonly focusTracker: FocusTracker;
134
+ /** DEBT-5 item 5 — the paste-flood guard's persistent state, mutated in place across tokens by handlePanelFocusToken (see panel-paste-flood-guard.ts). Never reallocated. */
135
+ readonly panelBurstGuard: PanelBurstGuardState;
134
136
  readonly getHistory: () => InfiniteBuffer;
135
137
  readonly getViewportHeight: () => number;
136
138
  readonly getScrollTop: () => number;
@@ -180,17 +182,39 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
180
182
  const lineCount = history.getLineCount();
181
183
  const keybindings = context.keybindingsManager;
182
184
 
183
- // A single stdin chunk can tokenize into several 'text' tokens (fast-typed
184
- // burst) or one 'text' token with a multi-char value (bracketed paste).
185
- // Computed once per feed() call, not per token, so per-token cost stays
186
- // the same O(1) check it always was.
187
- let totalPrintableChars = 0;
188
- for (const t of tokens) {
189
- if (t.type === 'text') totalPrintableChars += t.value.length;
190
- }
191
- const isPrintableBurst = totalPrintableChars > 1;
185
+ // Shared opener for the Fleet panel: makes it visible AND transfers keyboard
186
+ // focus to it. panelManager.open() only makes the panel active focus is a
187
+ // separate axis (see PanelManager.focusPanels()/getFocusTarget()); without the
188
+ // focusPanels() call, j/k/i/K land silently in the composer until Tab. Used by
189
+ // the footer indicator's [Enter] and by F2 (W6.2 e: F2 and the footer
190
+ // indicator both subsume the retired process modal). Mirrors the Ctrl+P
191
+ // panel-picker launcher (ui-openers.ts openPanelPicker).
192
+ const openFleetPanel = (): void => {
193
+ context.panelManager.open('fleet');
194
+ context.panelManager.focusPanels();
195
+ context.panelFocused = true;
196
+ };
192
197
 
198
+ // Paste-ness is a per-TOKEN property, computed at the handlePanelFocusToken
199
+ // call below — never a per-feed character sum. The SDK tokenizer emits a
200
+ // bracketed paste (\x1b[?2004h, enabled in main.ts terminal init) as ONE
201
+ // 'text' token holding the whole payload, while discrete keystrokes — even
202
+ // several batched into one feed() by render-tick latency — arrive as
203
+ // separate 1-char 'text' tokens. The old per-feed sum misread two quick nav
204
+ // keystrokes (e.g. j then k in one drain) as a "burst" and yanked focus.
205
+ // DEBT-5 item 5's flood guard reuses that same per-token model but adds
206
+ // real timing: one `now` per feed() call (not per token) is intentional —
207
+ // a genuine flood delivers many tokens in one drain, and they should all
208
+ // measure as arriving "at once", not spread across meaningless sub-ms noise.
209
+ const now = Date.now();
193
210
  for (const token of tokens) {
211
+ // Focus-reporting tokens (CSI ?1004h, W2.3) never reach the composer or any
212
+ // modal route — consumed here, first, unconditionally. No render needed.
213
+ if (token.type === 'focus') {
214
+ context.focusTracker.setFocused(token.action === 'in');
215
+ continue;
216
+ }
217
+
194
218
  if (token.type === 'key' && context.keybindingsManager.matches('clear-cancel', token)) {
195
219
  context.handleCtrlC();
196
220
  continue;
@@ -210,6 +234,7 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
210
234
  mcpWorkspace: context.mcpWorkspace,
211
235
  sessionPickerModal: context.sessionPickerModal,
212
236
  profilePickerModal: context.profilePickerModal,
237
+ configModal: context.configModal,
213
238
  onboardingWizard: context.onboardingWizard,
214
239
  helpOverlayActive: context.helpOverlayActive,
215
240
  helpScrollOffset: context.helpScrollOffset,
@@ -224,9 +249,6 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
224
249
  getViewportHeight: context.getViewportHeight,
225
250
  requestRender: context.requestRender,
226
251
  handleEscape: context.handleEscape,
227
- liveTailModal: context.liveTailModal,
228
- processModal: context.processModal,
229
- agentDetailModal: context.agentDetailModal,
230
252
  contextInspectorModal: context.contextInspectorModal,
231
253
  modalOpened: context.modalOpened,
232
254
  filePicker: context.filePicker,
@@ -265,6 +287,12 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
265
287
  // focusTarget and self-heals it, and context.panelFocused was seeded from
266
288
  // it at feed entry — so no manual "unfocus if panels vanished" patch is
267
289
  // needed here anymore.
290
+ // Snapshot these four BEFORE dispatch so the write-back below can tell
291
+ // a pipeline-driven change (see comment there) from a stale copy.
292
+ const promptBefore = context.prompt;
293
+ const cursorPosBefore = context.cursorPos;
294
+ const commandModeBefore = context.commandMode;
295
+ const panelFocusedBefore = context.panelFocused;
268
296
  const shortcutState = {
269
297
  panelFocused: context.panelFocused,
270
298
  prompt: context.prompt,
@@ -298,10 +326,22 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
298
326
  killRing: context.killRing,
299
327
  };
300
328
  if (handleGlobalShortcutToken(shortcutState, token, viewportHeight)) {
301
- context.prompt = shortcutState.prompt;
302
- context.cursorPos = shortcutState.cursorPos;
303
- context.commandMode = shortcutState.commandMode;
304
- context.panelFocused = shortcutState.panelFocused;
329
+ // Some branches (handleEscape, handleCtrlC, handleUndo, handleRedo,
330
+ // handlePaste) mutate handler state directly and immediately sync it
331
+ // into this same `context` via syncFeedContextMutableFields — e.g.
332
+ // Escape closing the slash-command palette clears context.prompt and
333
+ // context.commandMode on the live handler mid-call. `shortcutState`
334
+ // above is a snapshot taken BEFORE that call, so applying it
335
+ // unconditionally would stomp the live update right back to its
336
+ // stale pre-action value (undoing the very close it just performed,
337
+ // and even re-arming the palette below). This is the same class of
338
+ // bug as the overlay-flag write-back guard in handler.ts's feed().
339
+ // Only apply the snapshot for a field the dispatched action did NOT
340
+ // already update out from under us.
341
+ context.prompt = context.prompt === promptBefore ? shortcutState.prompt : context.prompt;
342
+ context.cursorPos = context.cursorPos === cursorPosBefore ? shortcutState.cursorPos : context.cursorPos;
343
+ context.commandMode = context.commandMode === commandModeBefore ? shortcutState.commandMode : context.commandMode;
344
+ context.panelFocused = context.panelFocused === panelFocusedBefore ? shortcutState.panelFocused : context.panelFocused;
305
345
  if (context.commandMode) {
306
346
  if (!context.prompt.startsWith('/')) {
307
347
  context.commandMode = false;
@@ -330,7 +370,19 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
330
370
  panelManager: context.panelManager,
331
371
  keybindingsManager: context.keybindingsManager,
332
372
  onPanelInputConsumed: context.onPanelInputConsumed,
333
- isPrintableBurst,
373
+ now,
374
+ burstGuard: context.panelBurstGuard,
375
+ // Per-token paste classification (Invariant B): a paste is a single
376
+ // 'text' token whose value holds more than one character.
377
+ isPasteToken: token.type === 'text' && token.value.length > 1,
378
+ // One-shot honesty hint when a paste is dropped into a non-capturing
379
+ // focused panel (Invariant A: focus never silently flips to the composer).
380
+ onPasteDropped: (panelName: string) =>
381
+ context.commandContext?.print(
382
+ `paste ignored — focus is on ${panelName}; Tab returns to composer`,
383
+ ),
384
+ isTurnActive: () => context.commandContext?.isGenerating?.() ?? false,
385
+ cancelGeneration: () => context.commandContext?.cancelGeneration?.(),
334
386
  }, token);
335
387
  context.panelFocused = panelRoute.panelFocused;
336
388
  if (panelRoute.handled) {
@@ -340,7 +392,7 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
340
392
  const indicatorRoute = handleIndicatorFocusToken({
341
393
  indicatorFocused: context.indicatorFocused,
342
394
  modalOpened: context.modalOpened,
343
- processModal: context.processModal,
395
+ openFleetPanel,
344
396
  requestRender: context.requestRender,
345
397
  }, token);
346
398
  context.indicatorFocused = indicatorRoute.indicatorFocused;
@@ -422,7 +474,7 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
422
474
  commandRegistry: context.commandRegistry,
423
475
  autocomplete: context.autocomplete,
424
476
  blockActionsMenu: { open: (block: BlockMeta) => context.blockActionsMenu.open(block) },
425
- processModal: context.processModal,
477
+ openFleetPanel,
426
478
  modalOpened: context.modalOpened,
427
479
  saveUndoState: context.saveUndoState,
428
480
  breakUndoCoalesce: context.breakUndoCoalesce,
@@ -225,14 +225,12 @@ export function handleEscapeForHandler(handler: InputHandler): void {
225
225
  helpOverlayActive: handler.helpOverlayActive,
226
226
  shortcutsOverlayActive: handler.shortcutsOverlayActive,
227
227
  bookmarkModal: handler.bookmarkModal,
228
- agentDetailModal: handler.agentDetailModal,
229
- liveTailModal: handler.liveTailModal,
230
228
  settingsModal: handler.settingsModal,
231
229
  mcpWorkspace: handler.mcpWorkspace,
232
230
  sessionPickerModal: handler.sessionPickerModal,
233
231
  profilePickerModal: handler.profilePickerModal,
232
+ configModal: handler.configModal,
234
233
  contextInspectorModal: handler.contextInspectorModal,
235
- processModal: handler.processModal,
236
234
  modelPicker: handler.modelPicker,
237
235
  filePicker: handler.filePicker,
238
236
  blockActionsMenu: handler.blockActionsMenu,