@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.
Files changed (209) hide show
  1. package/CHANGELOG.md +130 -127
  2. package/README.md +32 -14
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
  6. package/src/cli/entrypoint.ts +2 -0
  7. package/src/core/alert-gating.ts +67 -0
  8. package/src/core/approval-alert.ts +72 -0
  9. package/src/core/budget-breach-notifier.ts +106 -0
  10. package/src/core/conversation-rendering.ts +19 -0
  11. package/src/core/conversation.ts +58 -0
  12. package/src/core/focus-tracker.ts +41 -0
  13. package/src/core/long-task-notifier.ts +33 -6
  14. package/src/core/stream-event-wiring.ts +104 -2
  15. package/src/core/stream-stall-watchdog.ts +41 -17
  16. package/src/core/system-message-router.ts +37 -51
  17. package/src/core/turn-cancellation.ts +20 -0
  18. package/src/core/turn-event-wiring.ts +64 -3
  19. package/src/export/cost-utils.ts +126 -8
  20. package/src/input/command-registry.ts +54 -1
  21. package/src/input/commands/checkpoint-runtime.ts +280 -0
  22. package/src/input/commands/codebase-runtime.ts +192 -0
  23. package/src/input/commands/diff-runtime.ts +61 -30
  24. package/src/input/commands/eval.ts +1 -1
  25. package/src/input/commands/health-runtime.ts +1 -1
  26. package/src/input/commands/image-runtime.ts +112 -0
  27. package/src/input/commands/intelligence-runtime.ts +11 -1
  28. package/src/input/commands/local-auth-runtime.ts +4 -1
  29. package/src/input/commands/local-runtime.ts +9 -3
  30. package/src/input/commands/marketplace-runtime.ts +2 -2
  31. package/src/input/commands/memory.ts +6 -1
  32. package/src/input/commands/operator-panel-runtime.ts +40 -24
  33. package/src/input/commands/planning-runtime.ts +26 -3
  34. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  35. package/src/input/commands/plugin-runtime.ts +2 -2
  36. package/src/input/commands/policy-dispatch.ts +21 -0
  37. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  38. package/src/input/commands/qrcode-runtime.ts +3 -3
  39. package/src/input/commands/recall-review.ts +35 -0
  40. package/src/input/commands/remote-runtime.ts +3 -3
  41. package/src/input/commands/runtime-services.ts +54 -13
  42. package/src/input/commands/services-runtime.ts +1 -1
  43. package/src/input/commands/session-content.ts +12 -0
  44. package/src/input/commands/session-workflow.ts +19 -1
  45. package/src/input/commands/settings-sync-runtime.ts +1 -1
  46. package/src/input/commands/share-runtime.ts +9 -2
  47. package/src/input/commands/shell-core.ts +15 -7
  48. package/src/input/commands/skills-runtime.ts +2 -8
  49. package/src/input/commands/subscription-runtime.ts +2 -2
  50. package/src/input/commands/test-runtime.ts +277 -0
  51. package/src/input/commands/websearch-runtime.ts +101 -0
  52. package/src/input/commands/work-plan-runtime.ts +36 -10
  53. package/src/input/commands/workstream-runtime.ts +338 -0
  54. package/src/input/commands.ts +12 -0
  55. package/src/input/config-modal-types.ts +161 -0
  56. package/src/input/config-modal.ts +377 -0
  57. package/src/input/feed-context-factory.ts +7 -8
  58. package/src/input/handler-command-route.ts +27 -17
  59. package/src/input/handler-content-actions.ts +22 -8
  60. package/src/input/handler-feed-routes.ts +107 -7
  61. package/src/input/handler-feed.ts +49 -13
  62. package/src/input/handler-interactions.ts +3 -3
  63. package/src/input/handler-modal-routes.ts +65 -0
  64. package/src/input/handler-modal-stack.ts +3 -5
  65. package/src/input/handler-modal-token-routes.ts +16 -49
  66. package/src/input/handler-picker-routes.ts +11 -94
  67. package/src/input/handler-shortcuts.ts +24 -14
  68. package/src/input/handler-types.ts +3 -6
  69. package/src/input/handler-ui-state.ts +10 -22
  70. package/src/input/handler.ts +10 -28
  71. package/src/input/keybindings.ts +22 -8
  72. package/src/input/model-picker-types.ts +24 -6
  73. package/src/input/model-picker.ts +58 -0
  74. package/src/input/panel-integration-actions.ts +9 -170
  75. package/src/input/settings-modal-data.ts +95 -0
  76. package/src/input/settings-modal-mutations.ts +6 -4
  77. package/src/main.ts +41 -44
  78. package/src/panels/agent-inspector-shared.ts +35 -11
  79. package/src/panels/base-panel.ts +22 -1
  80. package/src/panels/builtin/agent.ts +21 -107
  81. package/src/panels/builtin/development.ts +16 -62
  82. package/src/panels/builtin/knowledge.ts +8 -32
  83. package/src/panels/builtin/operations.ts +84 -428
  84. package/src/panels/builtin/session.ts +21 -112
  85. package/src/panels/builtin/shared.ts +9 -43
  86. package/src/panels/builtin-modals.ts +218 -0
  87. package/src/panels/builtin-panels.ts +5 -0
  88. package/src/panels/cost-tracker-panel.ts +63 -14
  89. package/src/panels/diff-panel.ts +123 -60
  90. package/src/panels/eval-registry.ts +60 -0
  91. package/src/panels/fleet-panel.ts +800 -0
  92. package/src/panels/fleet-read-model.ts +477 -0
  93. package/src/panels/fleet-steer.ts +92 -0
  94. package/src/panels/fleet-stop.ts +125 -0
  95. package/src/panels/fleet-tabs.ts +230 -0
  96. package/src/panels/fleet-transcript.ts +356 -0
  97. package/src/panels/git-panel.ts +9 -9
  98. package/src/panels/index.ts +7 -31
  99. package/src/panels/local-auth-panel.ts +10 -0
  100. package/src/panels/modals/hooks-modal.ts +187 -0
  101. package/src/panels/modals/keybindings-modal.ts +151 -0
  102. package/src/panels/modals/knowledge-modal.ts +158 -0
  103. package/src/panels/modals/local-auth-modal.ts +132 -0
  104. package/src/panels/modals/marketplace-modal.ts +218 -0
  105. package/src/panels/modals/memory-modal.ts +180 -0
  106. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  107. package/src/panels/modals/modal-theme.ts +36 -0
  108. package/src/panels/modals/pairing-modal.ts +144 -0
  109. package/src/panels/modals/planning-modal.ts +282 -0
  110. package/src/panels/modals/plugins-modal.ts +174 -0
  111. package/src/panels/modals/policy-modal.ts +256 -0
  112. package/src/panels/modals/provider-health-modal.ts +136 -0
  113. package/src/panels/modals/remote-modal.ts +115 -0
  114. package/src/panels/modals/sandbox-modal.ts +150 -0
  115. package/src/panels/modals/security-modal.ts +217 -0
  116. package/src/panels/modals/services-modal.ts +179 -0
  117. package/src/panels/modals/settings-sync-modal.ts +142 -0
  118. package/src/panels/modals/skills-modal.ts +189 -0
  119. package/src/panels/modals/subscription-modal.ts +172 -0
  120. package/src/panels/modals/work-plan-modal.ts +149 -0
  121. package/src/panels/panel-confirm-overlay.ts +72 -0
  122. package/src/panels/panel-manager.ts +108 -5
  123. package/src/panels/project-planning-answer-actions.ts +4 -2
  124. package/src/panels/scrollable-list-panel.ts +9 -0
  125. package/src/panels/skills-panel.ts +7 -51
  126. package/src/panels/token-budget-panel.ts +5 -3
  127. package/src/panels/types.ts +26 -0
  128. package/src/permissions/hunk-selection.ts +179 -0
  129. package/src/permissions/prompt.ts +60 -4
  130. package/src/renderer/compaction-history-modal.ts +19 -3
  131. package/src/renderer/compaction-preview.ts +13 -2
  132. package/src/renderer/compaction-quality.ts +100 -0
  133. package/src/renderer/config-modal.ts +81 -0
  134. package/src/renderer/conversation-overlays.ts +10 -17
  135. package/src/renderer/fleet-tab-strip.ts +56 -0
  136. package/src/renderer/footer-tips.ts +10 -2
  137. package/src/renderer/git-status.ts +40 -0
  138. package/src/renderer/help-overlay.ts +2 -2
  139. package/src/renderer/model-workspace.ts +44 -1
  140. package/src/renderer/panel-workspace-bar.ts +8 -2
  141. package/src/renderer/shell-surface.ts +8 -1
  142. package/src/renderer/turn-injection.ts +114 -0
  143. package/src/renderer/ui-factory.ts +91 -7
  144. package/src/runtime/bootstrap-command-context.ts +24 -1
  145. package/src/runtime/bootstrap-command-parts.ts +36 -7
  146. package/src/runtime/bootstrap-core.ts +19 -4
  147. package/src/runtime/bootstrap-hook-bridge.ts +5 -0
  148. package/src/runtime/bootstrap-shell.ts +41 -17
  149. package/src/runtime/bootstrap.ts +11 -17
  150. package/src/runtime/code-index-services.ts +112 -0
  151. package/src/runtime/orchestrator-core-services.ts +49 -0
  152. package/src/runtime/process-lifecycle.ts +3 -1
  153. package/src/runtime/services.ts +91 -43
  154. package/src/runtime/ui-services.ts +8 -0
  155. package/src/runtime/workstream-services.ts +195 -0
  156. package/src/shell/blocking-input.ts +22 -1
  157. package/src/shell/ui-openers.ts +129 -17
  158. package/src/utils/format-duration.ts +8 -18
  159. package/src/utils/splash-lines.ts +1 -1
  160. package/src/version.ts +1 -1
  161. package/src/panels/agent-inspector-panel.ts +0 -786
  162. package/src/panels/approval-panel.ts +0 -252
  163. package/src/panels/automation-control-panel.ts +0 -479
  164. package/src/panels/cockpit-panel.ts +0 -481
  165. package/src/panels/cockpit-read-model.ts +0 -233
  166. package/src/panels/communication-panel.ts +0 -256
  167. package/src/panels/control-plane-panel.ts +0 -470
  168. package/src/panels/debug-panel.ts +0 -609
  169. package/src/panels/docs-panel.ts +0 -375
  170. package/src/panels/eval-panel.ts +0 -627
  171. package/src/panels/file-explorer-panel.ts +0 -664
  172. package/src/panels/file-preview-panel.ts +0 -517
  173. package/src/panels/hooks-panel.ts +0 -401
  174. package/src/panels/incident-review-panel.ts +0 -406
  175. package/src/panels/intelligence-panel.ts +0 -383
  176. package/src/panels/knowledge-graph-panel.ts +0 -506
  177. package/src/panels/marketplace-panel.ts +0 -399
  178. package/src/panels/memory-panel.ts +0 -558
  179. package/src/panels/ops-control-panel.ts +0 -329
  180. package/src/panels/ops-strategy-panel.ts +0 -321
  181. package/src/panels/orchestration-panel.ts +0 -465
  182. package/src/panels/panel-list-panel.ts +0 -557
  183. package/src/panels/plan-dashboard-panel.ts +0 -707
  184. package/src/panels/policy-panel.ts +0 -517
  185. package/src/panels/project-planning-panel.ts +0 -721
  186. package/src/panels/provider-health-panel.ts +0 -678
  187. package/src/panels/provider-health-tracker.ts +0 -306
  188. package/src/panels/provider-health-views.ts +0 -560
  189. package/src/panels/qr-panel.ts +0 -280
  190. package/src/panels/remote-panel.ts +0 -534
  191. package/src/panels/routes-panel.ts +0 -241
  192. package/src/panels/sandbox-panel.ts +0 -456
  193. package/src/panels/security-panel.ts +0 -447
  194. package/src/panels/services-panel.ts +0 -329
  195. package/src/panels/session-browser-panel.ts +0 -487
  196. package/src/panels/settings-sync-panel.ts +0 -398
  197. package/src/panels/subscription-panel.ts +0 -342
  198. package/src/panels/symbol-outline-panel.ts +0 -619
  199. package/src/panels/system-messages-panel.ts +0 -364
  200. package/src/panels/tasks-panel.ts +0 -608
  201. package/src/panels/thinking-panel.ts +0 -333
  202. package/src/panels/tool-inspector-panel.ts +0 -537
  203. package/src/panels/work-plan-panel.ts +0 -530
  204. package/src/panels/worktree-panel.ts +0 -360
  205. package/src/panels/wrfc-panel.ts +0 -790
  206. package/src/renderer/agent-detail-modal.ts +0 -465
  207. package/src/renderer/live-tail-modal.ts +0 -156
  208. package/src/renderer/process-modal.ts +0 -656
  209. package/src/renderer/qr-renderer.ts +0 -120
@@ -5,7 +5,6 @@ import type { CapabilityFilter, CategoryFilter, ModelPickerModal } from './model
5
5
  import { MODEL_PICKER_CHROME_LINES } from '../renderer/model-picker-overlay.ts';
6
6
  import { resolveAndValidatePath } from '@pellux/goodvibes-sdk/platform/utils';
7
7
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
8
- import type { ProcessEntry } from '../renderer/process-modal.ts';
9
8
  import type { BlockActionId } from '../renderer/block-actions.ts';
10
9
 
11
10
  type ModelPickerRouteState = {
@@ -93,6 +92,13 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
93
92
  }
94
93
  state.modelPicker.close();
95
94
  if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
95
+ } else if (mode === 'embeddingProvider') {
96
+ const selectedProvider = state.modelPicker.embeddingProviders[idx];
97
+ if (selectedProvider) {
98
+ state.commandContext?.completeEmbeddingProviderSelection?.(selectedProvider.id);
99
+ }
100
+ state.modelPicker.close();
101
+ if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
96
102
  } else if (mode === 'contextCap') {
97
103
  const capModel = state.modelPicker.contextCapPendingModel;
98
104
  if (capModel) {
@@ -201,99 +207,10 @@ function cycleCapabilityFilter(modelPicker: ModelPickerModal): void {
201
207
  modelPicker.setCapabilityFilter(cycle[(cur + 1) % cycle.length]!);
202
208
  }
203
209
 
204
- type ProcessRouteState = {
205
- processModal: {
206
- active: boolean;
207
- moveUp: () => void;
208
- moveDown: () => void;
209
- getSelected: () => ProcessEntry | undefined;
210
- close: () => void;
211
- open: () => void;
212
- killSelected: () => boolean;
213
- refresh: () => void;
214
- };
215
- liveTailModal: {
216
- open: (entry: ProcessEntry) => void;
217
- };
218
- agentDetailModal: {
219
- open: (id: string) => void;
220
- };
221
- modalOpened: (name: string) => void;
222
- requestRender: () => void;
223
- handleEscape: () => void;
224
- };
225
-
226
- export function handleProcessModalToken(state: ProcessRouteState, token: InputToken): boolean {
227
- if (!state.processModal.active) return false;
228
-
229
- if (token.type === 'key') {
230
- if (token.logicalName === 'escape') {
231
- state.handleEscape();
232
- return true;
233
- }
234
- if (token.logicalName === 'up') state.processModal.moveUp();
235
- else if (token.logicalName === 'down') state.processModal.moveDown();
236
- else if (token.logicalName === 'enter') {
237
- const entry = state.processModal.getSelected();
238
- if (entry) {
239
- if (entry.type === 'agent') {
240
- state.modalOpened('agentDetail');
241
- state.processModal.close();
242
- state.agentDetailModal.open(entry.id);
243
- } else {
244
- state.modalOpened('liveTail');
245
- state.processModal.close();
246
- state.liveTailModal.open(entry);
247
- }
248
- }
249
- }
250
- } else if (token.type === 'text' && token.value === 'k') {
251
- const killed = state.processModal.killSelected();
252
- if (killed) state.processModal.refresh();
253
- }
254
-
255
- state.requestRender();
256
- return true;
257
- }
258
-
259
- type LiveTailRouteState = {
260
- liveTailModal: {
261
- active: boolean;
262
- scrollUp: () => void;
263
- scrollDown: () => void;
264
- killProcess: () => void;
265
- close: () => void;
266
- };
267
- processModal: {
268
- open: () => void;
269
- };
270
- requestRender: () => void;
271
- handleEscape: () => void;
272
- };
273
-
274
- export function handleLiveTailToken(state: LiveTailRouteState, token: InputToken): boolean {
275
- if (!state.liveTailModal.active) return false;
276
-
277
- const killAndReturn = (): void => {
278
- state.liveTailModal.killProcess();
279
- state.handleEscape();
280
- };
281
-
282
- if (token.type === 'key') {
283
- if (token.logicalName === 'escape') {
284
- state.handleEscape();
285
- return true;
286
- }
287
- if (token.logicalName === 'up') state.liveTailModal.scrollUp();
288
- else if (token.logicalName === 'down') state.liveTailModal.scrollDown();
289
- else if (token.logicalName === 'k') killAndReturn();
290
- } else if (token.type === 'text' && token.value === 'k') {
291
- killAndReturn();
292
- }
293
-
294
- state.requestRender();
295
- return true;
296
- }
210
+ // W6.2 e / W6.1 retirement: the process-modal and live-tail-modal token routes
211
+ // were removed with those modals (F2 now opens Fleet, which subsumes the live
212
+ // process tree). ProcessModal/AgentDetailModal/LiveTailModal were structurally
213
+ // unreachable after the F2 repoint and were deleted.
297
214
 
298
215
  type EscapeOnlyModalRouteState = {
299
216
  active: boolean;
@@ -55,13 +55,16 @@ export function handleGlobalShortcutToken(
55
55
  ): boolean {
56
56
  if (token.type !== 'key') return false;
57
57
 
58
- // Fast-path: bare pageup/pagedown have no keybinding entry.
59
- if (token.logicalName === 'pageup') {
58
+ // Fast-path: BARE pageup/pagedown scroll the transcript. The `!token.ctrl`
59
+ // guard is load-bearing: Ctrl+PageUp/PageDown are the panel-tab-prev/next
60
+ // chords, so they must fall through to the keybinding lookup below instead of
61
+ // being swallowed here as a scroll.
62
+ if (token.logicalName === 'pageup' && !token.ctrl) {
60
63
  if (state.panelFocused) return false;
61
64
  state.scroll(-Math.max(1, viewportHeight - 2));
62
65
  return true;
63
66
  }
64
- if (token.logicalName === 'pagedown') {
67
+ if (token.logicalName === 'pagedown' && !token.ctrl) {
65
68
  if (state.panelFocused) return false;
66
69
  state.scroll(Math.max(1, viewportHeight - 2));
67
70
  return true;
@@ -101,6 +104,13 @@ export function handleGlobalShortcutToken(
101
104
  case 'panel-close': {
102
105
  const pm = state.panelManager;
103
106
  const active = pm.getActivePanel();
107
+ // Wave-3: give the active panel a chance to consume Ctrl+X for an
108
+ // in-panel action (FleetPanel session-tab detach) before it closes the
109
+ // panel outright — see Panel.interceptPanelClose's doc comment.
110
+ if (active?.interceptPanelClose?.()) {
111
+ state.requestRender();
112
+ return true;
113
+ }
104
114
  if (active) {
105
115
  pm.close(active.id);
106
116
  state.requestRender();
@@ -160,17 +170,17 @@ export function handleGlobalShortcutToken(
160
170
  }
161
171
 
162
172
  case 'panel-ops': {
163
- // Ctrl+O: open the Ops Control panel. Prefer the command-context callback
164
- // (wired only when the operator-control-plane feature flag is on) so it
165
- // reuses whatever show/open semantics bootstrap.ts set up; otherwise fall
166
- // back to opening the always-registered 'ops-control' panel type directly
167
- // it renders an honest not-configured state when its deps are absent.
168
- if (state.commandContext?.openOpsPanel) {
169
- state.commandContext.openOpsPanel();
170
- } else {
171
- state.panelManager.open('ops-control');
172
- state.requestRender();
173
- }
173
+ // Ctrl+O: open AND focus the Fleet panel. The former Ops Control panel was
174
+ // retired to an 'ops-control' -> 'fleet' alias (W6.1); rather than route
175
+ // through the now-aliased openOpsPanel callback (which opens without
176
+ // transferring focus), open 'fleet' directly and grab focus — mirroring
177
+ // the footer indicator's [Enter] -> openFleetPanel path so j/k/i/K land in
178
+ // the panel immediately instead of the composer.
179
+ const pm = state.panelManager;
180
+ pm.open('fleet');
181
+ pm.focusPanels();
182
+ state.panelFocused = true;
183
+ state.requestRender();
174
184
  return true;
175
185
  }
176
186
 
@@ -23,14 +23,12 @@ import type { SelectionManager } from './selection.ts';
23
23
  import type { InfiniteBuffer } from '../core/history.ts';
24
24
  import type { AutocompleteEngine } from './autocomplete.ts';
25
25
  import type { BookmarkModal } from './bookmark-modal.ts';
26
- import type { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
27
- import type { LiveTailModal } from '../renderer/live-tail-modal.ts';
28
26
  import type { SettingsModal } from './settings-modal.ts';
29
27
  import type { McpWorkspace } from './mcp-workspace.ts';
30
28
  import type { SessionPickerModal } from './session-picker-modal.ts';
29
+ import type { ConfigModal } from './config-modal.ts';
31
30
  import type { ProfilePickerModal } from './profile-picker-modal.ts';
32
31
  import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
33
- import type { ProcessModal } from '../renderer/process-modal.ts';
34
32
  import type { FilePickerModal } from './file-picker.ts';
35
33
  import type { BlockActionsMenu } from '../renderer/block-actions.ts';
36
34
  import type { SelectionModal } from './selection-modal.ts';
@@ -88,6 +86,7 @@ export interface InputHandlerLike {
88
86
  lastCopyTime: number;
89
87
  lastBlockCopyTime: number;
90
88
  lastCtrlCTime: number;
89
+ lastCtrlCTimeoutId: ReturnType<typeof setTimeout> | null;
91
90
 
92
91
  // ── Modal state ───────────────────────────────────────────────────────────
93
92
  commandMode: boolean;
@@ -104,14 +103,12 @@ export interface InputHandlerLike {
104
103
 
105
104
  // ── Modal objects ─────────────────────────────────────────────────────────
106
105
  bookmarkModal: BookmarkModal;
107
- agentDetailModal: AgentDetailModal;
108
- liveTailModal: LiveTailModal;
109
106
  settingsModal: SettingsModal;
110
107
  mcpWorkspace: McpWorkspace;
111
108
  sessionPickerModal: SessionPickerModal;
112
109
  profilePickerModal: ProfilePickerModal;
110
+ configModal: ConfigModal;
113
111
  contextInspectorModal: ContextInspectorModal;
114
- processModal: ProcessModal;
115
112
  modelPicker: ModelPickerModal;
116
113
  filePicker: FilePickerModal;
117
114
  blockActionsMenu: BlockActionsMenu;
@@ -55,14 +55,12 @@ export type ActiveModalState = {
55
55
  helpOverlayActive: boolean;
56
56
  shortcutsOverlayActive: boolean;
57
57
  bookmarkModal: { active: boolean; close: () => void };
58
- agentDetailModal: { active: boolean; close: () => void };
59
- liveTailModal: { active: boolean; close: () => void };
60
58
  settingsModal: { active: boolean; close: () => void };
61
59
  mcpWorkspace?: { active: boolean; close: () => void; reopen: () => void };
62
60
  sessionPickerModal: { active: boolean; close: () => void };
63
61
  profilePickerModal: { active: boolean; close: () => void };
62
+ configModal: { active: boolean; close: () => void };
64
63
  contextInspectorModal: { active: boolean; close: () => void };
65
- processModal: { active: boolean; close: () => void };
66
64
  modelPicker: { active: boolean; close: () => void };
67
65
  filePicker: { active: boolean; close: () => void };
68
66
  blockActionsMenu: { active: boolean; close: () => void };
@@ -75,14 +73,12 @@ export function getActiveModalName(state: ActiveModalState): string | null {
75
73
  if (state.helpOverlayActive) return 'help';
76
74
  if (state.shortcutsOverlayActive) return 'shortcuts';
77
75
  if (state.bookmarkModal.active) return 'bookmark';
78
- if (state.agentDetailModal.active) return 'agentDetail';
79
- if (state.liveTailModal.active) return 'liveTail';
80
76
  if (state.settingsModal.active) return 'settings';
81
77
  if (state.mcpWorkspace?.active) return 'mcpWorkspace';
82
78
  if (state.sessionPickerModal.active) return 'sessionPicker';
83
79
  if (state.profilePickerModal.active) return 'profilePicker';
80
+ if (state.configModal.active) return 'config';
84
81
  if (state.contextInspectorModal.active) return 'contextInspector';
85
- if (state.processModal.active) return 'process';
86
82
  if (state.modelPicker.active) return 'modelPicker';
87
83
  if (state.filePicker.active) return 'filePicker';
88
84
  if (state.blockActionsMenu.active) return 'blockActions';
@@ -96,14 +92,12 @@ export type ModalCloseOps = {
96
92
  resetHelp: () => void;
97
93
  resetShortcuts: () => void;
98
94
  closeBookmark: () => void;
99
- closeAgentDetail: () => void;
100
- closeLiveTail: () => void;
101
95
  closeSettings: () => void;
102
96
  closeMcpWorkspace: () => void;
103
97
  closeSessionPicker: () => void;
104
98
  closeProfilePicker: () => void;
99
+ closeConfigModal: () => void;
105
100
  closeContextInspector: () => void;
106
- closeProcess: () => void;
107
101
  closeModelPicker: () => void;
108
102
  closeFilePicker: () => void;
109
103
  closeBlockActions: () => void;
@@ -123,12 +117,6 @@ export function closeModalByName(name: string, ops: ModalCloseOps): void {
123
117
  case 'bookmark':
124
118
  ops.closeBookmark();
125
119
  break;
126
- case 'agentDetail':
127
- ops.closeAgentDetail();
128
- break;
129
- case 'liveTail':
130
- ops.closeLiveTail();
131
- break;
132
120
  case 'settings':
133
121
  ops.closeSettings();
134
122
  break;
@@ -141,12 +129,12 @@ export function closeModalByName(name: string, ops: ModalCloseOps): void {
141
129
  case 'profilePicker':
142
130
  ops.closeProfilePicker();
143
131
  break;
132
+ case 'config':
133
+ ops.closeConfigModal();
134
+ break;
144
135
  case 'contextInspector':
145
136
  ops.closeContextInspector();
146
137
  break;
147
- case 'process':
148
- ops.closeProcess();
149
- break;
150
138
  case 'modelPicker':
151
139
  ops.closeModelPicker();
152
140
  break;
@@ -172,10 +160,10 @@ export type ModalOpenOps = {
172
160
  openHelp: () => void;
173
161
  openShortcuts: () => void;
174
162
  openBookmark: () => void;
175
- openProcess: () => void;
176
163
  openContextInspector: () => void;
177
164
  openMcpWorkspace?: () => void;
178
165
  openOnboarding?: () => void;
166
+ openConfigModal?: () => void;
179
167
  openCommandMode: () => void;
180
168
  };
181
169
 
@@ -190,15 +178,15 @@ export function reopenModalByName(name: string, ops: ModalOpenOps): void {
190
178
  case 'bookmark':
191
179
  ops.openBookmark();
192
180
  break;
193
- case 'process':
194
- ops.openProcess();
195
- break;
196
181
  case 'contextInspector':
197
182
  ops.openContextInspector();
198
183
  break;
199
184
  case 'mcpWorkspace':
200
185
  ops.openMcpWorkspace?.();
201
186
  break;
187
+ case 'config':
188
+ ops.openConfigModal?.();
189
+ break;
202
190
  case 'onboarding':
203
191
  ops.openOnboarding?.();
204
192
  break;
@@ -19,15 +19,13 @@ import type { SelectionResult, SelectionAction } from './selection-modal.ts';
19
19
  import { SearchManager } from './search.ts';
20
20
  import { InputHistory, HistorySearch } from './input-history.ts';
21
21
  import type { BlockMeta, ConversationManager } from '../core/conversation';
22
- import { ProcessModal } from '../renderer/process-modal.ts';
23
- import { LiveTailModal } from '../renderer/live-tail-modal.ts';
24
22
  import { BlockActionsMenu } from '../renderer/block-actions.ts';
25
- import { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
26
23
  import { ContextInspectorModal } from '../renderer/context-inspector.ts';
27
24
  import { BookmarkModal } from './bookmark-modal.ts';
28
25
  import { SettingsModal } from './settings-modal.ts';
29
26
  import { McpWorkspace } from './mcp-workspace.ts';
30
27
  import { SessionPickerModal } from './session-picker-modal.ts';
28
+ import { ConfigModal } from './config-modal.ts';
31
29
  import { ProfilePickerModal } from './profile-picker-modal.ts';
32
30
  import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
33
31
  import {
@@ -106,9 +104,7 @@ import {
106
104
  handleBlockActionsToken,
107
105
  handleEscapeOnlyModalToken,
108
106
  handleFilePickerToken,
109
- handleLiveTailToken,
110
107
  handleModelPickerToken,
111
- handleProcessModalToken,
112
108
  } from './handler-picker-routes.ts';
113
109
  import { handleGlobalShortcutToken } from './handler-shortcuts.ts';
114
110
  import { feedInputTokens } from './handler-feed.ts';
@@ -172,6 +168,10 @@ export class InputHandler implements InputHandlerLike {
172
168
  public pasteRegistry = new Map<string, string>();
173
169
  public nextPasteId = 1;
174
170
  public lastCtrlCTime = 0;
171
+ /** Pending "hide the exit notice" timer from the last empty-prompt Ctrl+C
172
+ * press, if any — cleared before every subsequent press decides its own
173
+ * outcome (see handleCtrlC in handler-content-actions.ts). */
174
+ public lastCtrlCTimeoutId: ReturnType<typeof setTimeout> | null = null;
175
175
  /** Long-lived feed context — reused across every feed() call to avoid per-keystroke allocation. */
176
176
  public feedContext!: import('./handler-feed.ts').InputFeedContext;
177
177
  public commandRegistry: CommandRegistry | null = null;
@@ -180,13 +180,11 @@ export class InputHandler implements InputHandlerLike {
180
180
  public modelPicker: ModelPickerModal;
181
181
  public selectionModal = new SelectionModal();
182
182
  public searchManager = new SearchManager();
183
- public processModal: ProcessModal;
184
- public liveTailModal: LiveTailModal;
185
- public agentDetailModal: AgentDetailModal;
186
183
  public contextInspectorModal = new ContextInspectorModal();
187
184
  public bookmarkModal: BookmarkModal;
188
185
  public blockActionsMenu = new BlockActionsMenu();
189
186
  public settingsModal = new SettingsModal();
187
+ public configModal = new ConfigModal();
190
188
  public mcpWorkspace = new McpWorkspace();
191
189
  public onboardingWizard = new OnboardingWizardController();
192
190
  public onboardingModelPickerCancelSnapshot: OnboardingWizardSnapshot | null = null;
@@ -268,23 +266,8 @@ export class InputHandler implements InputHandlerLike {
268
266
  uiServices.providers.benchmarkStore,
269
267
  uiServices.providers.providerRegistry,
270
268
  );
271
- this.processModal = new ProcessModal({
272
- agentManager: uiServices.agents.agentManager,
273
- processManager: uiServices.shell.processManager,
274
- wrfcController: uiServices.agents.wrfcController,
275
- });
276
- this.liveTailModal = new LiveTailModal({
277
- agentManager: uiServices.agents.agentManager,
278
- processManager: uiServices.shell.processManager,
279
- });
280
- this.agentDetailModal = new AgentDetailModal({
281
- agentManager: uiServices.agents.agentManager,
282
- agentMessageBus: uiServices.agents.agentMessageBus,
283
- sessionLogPathResolver: (agentId) => uiServices.environment.shellPaths.resolveProjectPath('tui', 'sessions', `${agentId}.jsonl`),
284
- // SDK 0.23.0: supply wrfcController so the modal can show constraint data
285
- wrfcController: uiServices.agents.wrfcController,
286
- cancelAgent: (agentId: string) => uiServices.agents.agentManager.cancel(agentId),
287
- });
269
+ // W6.1 retirement: ProcessModal/LiveTailModal/AgentDetailModal were removed
270
+ // — F2 now opens the Fleet panel, which subsumes the live process tree.
288
271
  this.bookmarkModal = new BookmarkModal(uiServices.shell.bookmarkManager);
289
272
  this.sessionPickerModal = new SessionPickerModal(uiServices.sessions.sessionManager);
290
273
  this.profilePickerModal = new ProfilePickerModal(uiServices.shell.profileManager);
@@ -318,6 +301,7 @@ export class InputHandler implements InputHandlerLike {
318
301
  mcpWorkspace: this.mcpWorkspace,
319
302
  sessionPickerModal: this.sessionPickerModal,
320
303
  profilePickerModal: this.profilePickerModal,
304
+ configModal: this.configModal,
321
305
  historySearch: this.historySearch,
322
306
  commandRegistry: this.commandRegistry,
323
307
  commandContext: this.commandContext,
@@ -325,9 +309,6 @@ export class InputHandler implements InputHandlerLike {
325
309
  filePicker: this.filePicker,
326
310
  modelPicker: this.modelPicker,
327
311
  onboardingWizard: this.onboardingWizard,
328
- processModal: this.processModal,
329
- liveTailModal: this.liveTailModal,
330
- agentDetailModal: this.agentDetailModal,
331
312
  contextInspectorModal: this.contextInspectorModal,
332
313
  blockActionsMenu: this.blockActionsMenu,
333
314
  searchManager: this.searchManager,
@@ -337,6 +318,7 @@ export class InputHandler implements InputHandlerLike {
337
318
  panelManager: this.uiServices.shell.panelManager,
338
319
  keybindingsManager: this.uiServices.shell.keybindingsManager,
339
320
  killRing: this.killRing,
321
+ focusTracker: this.uiServices.platform.focusTracker,
340
322
  getHistory: this.getHistory,
341
323
  getViewportHeight: this.getViewportHeight,
342
324
  getScrollTop: this.getScrollTop,
@@ -88,7 +88,7 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
88
88
  'panel-tab-7': 'Jump to workspace panel tab 7',
89
89
  'panel-tab-8': 'Jump to workspace panel tab 8',
90
90
  'panel-tab-9': 'Jump to workspace panel tab 9',
91
- 'panel-ops': 'Open the Ops Control panel',
91
+ 'panel-ops': 'Open and focus the Fleet panel',
92
92
  'panel-focus-toggle': 'Switch keyboard focus between top and bottom pane',
93
93
  'history-search': 'Reverse input history search',
94
94
  'search': 'Toggle conversation search',
@@ -119,8 +119,14 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
119
119
  'panel-picker': [{ key: 'p', ctrl: true }],
120
120
  'panel-close': [{ key: 'x', ctrl: true }],
121
121
  'panel-close-all': [{ key: 'x', ctrl: true, shift: true }],
122
- 'panel-tab-next': [{ key: ']', ctrl: true }],
123
- 'panel-tab-prev': [{ key: '[', ctrl: true }],
122
+ // Ctrl+] stays the primary next-tab chord; Ctrl+PageDown is added as a second
123
+ // binding. Ctrl+[ was REMOVED as prev-tab: it is byte 0x1B (ESC), so the
124
+ // legacy tokenizer path emits it as 'escape' — the binding never matched on
125
+ // most terminals AND the key fired Escape (a split-brain chord). Ctrl+PageUp
126
+ // replaces it: Ctrl+PageUp/PageDown tokenize consistently (\x1b[5;5~ /
127
+ // \x1b[6;5~ -> pageup/pagedown ctrl:true) in both the legacy and CSI-u paths.
128
+ 'panel-tab-next': [{ key: ']', ctrl: true }, { key: 'pagedown', ctrl: true }],
129
+ 'panel-tab-prev': [{ key: 'pageup', ctrl: true }],
124
130
  // Alt+1..9: jump directly to the Nth workspace panel tab (across both panes).
125
131
  // The tokenizer delivers Alt as the token's `meta` modifier; comboMatches /
126
132
  // lookup treat `meta` as an alias for `alt`, so these alt-combos route through
@@ -134,10 +140,10 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
134
140
  'panel-tab-7': [{ key: '7', alt: true }],
135
141
  'panel-tab-8': [{ key: '8', alt: true }],
136
142
  'panel-tab-9': [{ key: '9', alt: true }],
137
- // Ctrl+O: open the Ops Control panel (operator intervention console).
138
- // Routed globally in handleGlobalShortcutToken: prefers commandContext.openOpsPanel()
139
- // when the operator-control-plane feature flag wired it, else falls back to opening
140
- // the always-registered 'ops-control' panel type directly via the panel manager.
143
+ // Ctrl+O: open and focus the Fleet panel. The former Ops Control panel was
144
+ // retired to an 'ops-control' -> 'fleet' alias (W6.1); the binding is KEPT
145
+ // (repointed, not removed) so the Ctrl+O muscle memory still lands somewhere
146
+ // useful. Routed globally in handleGlobalShortcutToken.
141
147
  'panel-ops': [{ key: 'o', ctrl: true }],
142
148
  // Ctrl+G: toggle keyboard focus between the top and bottom panes. Ctrl+G is
143
149
  // otherwise unbound in the default table.
@@ -355,7 +361,15 @@ export class KeybindingsManager {
355
361
  if (combo.ctrl) parts.push('Ctrl');
356
362
  if (combo.alt) parts.push('Alt');
357
363
  if (combo.shift) parts.push('Shift');
358
- parts.push(combo.key.length === 1 ? combo.key.toUpperCase() : combo.key);
364
+ // Friendly display for named keys with a conventional abbreviation, so a
365
+ // chord like Ctrl+PageUp reads consistently with the "PageUp / PageDn"
366
+ // scroll help text. Single chars upper-case; other named keys pass through.
367
+ const KEY_DISPLAY: Record<string, string> = { pageup: 'PageUp', pagedown: 'PageDn' };
368
+ parts.push(
369
+ combo.key.length === 1
370
+ ? combo.key.toUpperCase()
371
+ : KEY_DISPLAY[combo.key] ?? combo.key,
372
+ );
359
373
  return parts.join('+');
360
374
  }
361
375
 
@@ -1,15 +1,17 @@
1
1
  import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
2
2
 
3
- export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap';
3
+ export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap' | 'embeddingProvider';
4
4
 
5
5
  /**
6
6
  * Which config keys the model picker writes to on commit.
7
- * 'main' -> provider.provider + provider.model (default)
8
- * 'helper' -> helper.globalProvider + helper.globalModel (+ helper.enabled: true)
9
- * 'tool' -> tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
10
- * 'tts' -> tts.llmProvider + tts.llmModel
7
+ * 'main' -> provider.provider + provider.model (default)
8
+ * 'helper' -> helper.globalProvider + helper.globalModel (+ helper.enabled: true)
9
+ * 'tool' -> tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
10
+ * 'tts' -> tts.llmProvider + tts.llmModel
11
+ * 'embeddings' -> provider.embeddingProvider, via MemoryEmbeddingProviderRegistry.setDefaultProvider()
12
+ * (not an LLM route — no model concept, see ModelPickerTargetInfo.configuredNote)
11
13
  */
12
- export type ModelPickerTarget = 'main' | 'helper' | 'tool' | 'tts';
14
+ export type ModelPickerTarget = 'main' | 'helper' | 'tool' | 'tts' | 'embeddings';
13
15
 
14
16
  export type ModelPickerFocusPane = 'targets' | 'items';
15
17
 
@@ -21,6 +23,22 @@ export interface ModelPickerTargetInfo {
21
23
  readonly model: string;
22
24
  readonly enabled: boolean;
23
25
  readonly inherited: boolean;
26
+ /**
27
+ * Honest override for the "Current:" summary line. Used by the 'embeddings'
28
+ * target (which has no model concept — only a provider id + dimensions +
29
+ * configured state) so the renderer never prints a phantom "model:" value.
30
+ * When set, this replaces the computed provider:model route text.
31
+ */
32
+ readonly configuredNote?: string;
33
+ }
34
+
35
+ /** One entry in the embedding-provider picker list (PickerMode 'embeddingProvider'). */
36
+ export interface EmbeddingProviderPickerEntry {
37
+ readonly id: string;
38
+ readonly label: string;
39
+ readonly dimensions: number;
40
+ readonly configured: boolean;
41
+ readonly detail?: string;
24
42
  }
25
43
 
26
44
  /**
@@ -7,6 +7,7 @@ import type {
7
7
  BenchmarkSort,
8
8
  CapabilityFilter,
9
9
  CategoryFilter,
10
+ EmbeddingProviderPickerEntry,
10
11
  FilteredModelsCache,
11
12
  FilteredProvidersCache,
12
13
  GroupByMode,
@@ -40,6 +41,7 @@ export type {
40
41
  BenchmarkSort,
41
42
  CapabilityFilter,
42
43
  CategoryFilter,
44
+ EmbeddingProviderPickerEntry,
43
45
  GroupByMode,
44
46
  ModelFamily,
45
47
  ModelPickerFocusPane,
@@ -86,6 +88,10 @@ export class ModelPickerModal {
86
88
  public models: ModelDefinition[] = [];
87
89
  public providers: string[] = [];
88
90
  public effortLevels: string[] = [];
91
+ /** Entries for PickerMode 'embeddingProvider' — the 'embeddings' target's own tiny item list. */
92
+ public embeddingProviders: EmbeddingProviderPickerEntry[] = [];
93
+ /** Mode to restore when navigating the targets rail away from 'embeddings'. */
94
+ private nonEmbeddingMode: PickerMode = 'model';
89
95
  /** The model chosen in model-mode, awaiting effort selection. */
90
96
  public pendingModel: ModelDefinition | null = null;
91
97
  /** The model awaiting context cap input (contextCap mode). */
@@ -148,9 +154,20 @@ export class ModelPickerModal {
148
154
  }
149
155
 
150
156
  setTarget(target: ModelPickerTarget): void {
157
+ const previousTarget = this.target;
151
158
  this.target = target;
152
159
  const idx = this.targetInfos.findIndex((entry) => entry.target === target);
153
160
  this.targetIndex = idx >= 0 ? idx : this.targetIndex;
161
+ // The 'embeddings' target routes through its own tiny item-list mode
162
+ // (embedding providers are not ModelDefinition-shaped) rather than
163
+ // whatever LLM mode the picker session was already in. Remember the
164
+ // mode we came from so tabbing back to another target restores it.
165
+ if (target === 'embeddings' && this.mode !== 'embeddingProvider') {
166
+ this.nonEmbeddingMode = this.mode;
167
+ this.mode = 'embeddingProvider';
168
+ } else if (previousTarget === 'embeddings' && target !== 'embeddings' && this.mode === 'embeddingProvider') {
169
+ this.mode = this.nonEmbeddingMode;
170
+ }
154
171
  this.alignSelectionToTarget();
155
172
  }
156
173
 
@@ -171,6 +188,13 @@ export class ModelPickerModal {
171
188
  this.selectedIndex = modelIdx >= 0 ? modelIdx : 0;
172
189
  this.scrollOffset = 0;
173
190
  this._scrollToSelection(20);
191
+ return;
192
+ }
193
+ if (this.mode === 'embeddingProvider') {
194
+ const providerIdx = this.embeddingProviders.findIndex((provider) => provider.id === info.provider);
195
+ this.selectedIndex = providerIdx >= 0 ? providerIdx : 0;
196
+ this.scrollOffset = 0;
197
+ this._scrollToSelection(20);
174
198
  }
175
199
  }
176
200
 
@@ -273,6 +297,27 @@ export class ModelPickerModal {
273
297
  this.scrollOffset = 0;
274
298
  }
275
299
 
300
+ /**
301
+ * Open the embedding-provider list — entry point for the 'embeddings' target.
302
+ * Deliberately NOT routed through openAllModels (ModelDefinition-shaped) or
303
+ * openProviders (LLM-provider label resolution) — embedding providers are a
304
+ * flat {id, label, dimensions, configured} list with no model concept, and
305
+ * reusing either existing opener would mislabel or drop unrecognized ids.
306
+ */
307
+ openEmbeddingProviders(providers: EmbeddingProviderPickerEntry[], currentId: string): void {
308
+ this.previousMode = null;
309
+ this.embeddingProviders = providers;
310
+ this.mode = 'embeddingProvider';
311
+ this.active = true;
312
+ this.pendingModel = null;
313
+ this.focusPane = 'items';
314
+ this.searchFocused = false;
315
+ this.query = '';
316
+ const idx = providers.findIndex((provider) => provider.id === currentId);
317
+ this.selectedIndex = idx >= 0 ? idx : 0;
318
+ this.scrollOffset = 0;
319
+ }
320
+
276
321
  /** Transition to model list filtered by provider (called from provider mode Enter). */
277
322
  showModelsForProvider(models: ModelDefinition[], _provider: string): void {
278
323
  this.previousMode = 'provider';
@@ -311,6 +356,8 @@ export class ModelPickerModal {
311
356
  this.targetIndex = 0;
312
357
  this.models = [];
313
358
  this.providers = [];
359
+ this.embeddingProviders = [];
360
+ this.nonEmbeddingMode = 'model';
314
361
  this.pendingModel = null;
315
362
  this.contextCapPendingModel = null;
316
363
  this.contextCapQuery = '';
@@ -473,6 +520,16 @@ export class ModelPickerModal {
473
520
  this.providerItemsCache = cache;
474
521
  return result;
475
522
  }
523
+ if (this.mode === 'embeddingProvider') {
524
+ // Tiny list (typically 2-4 entries) — no caching infrastructure needed.
525
+ // Unconfigured providers are shown honestly (isConfigured: false), never hidden.
526
+ return this.embeddingProviders.map((provider) => ({
527
+ id: provider.id,
528
+ label: provider.label,
529
+ detail: `${provider.dimensions}d${provider.configured ? '' : ' · unconfigured'}`,
530
+ isConfigured: provider.configured,
531
+ }));
532
+ }
476
533
  // effort mode
477
534
  return buildEffortItems(this.effortLevels);
478
535
  }
@@ -481,6 +538,7 @@ export class ModelPickerModal {
481
538
  getItemCount(): number {
482
539
  if (this.mode === 'model') return this.getFilteredModels().length;
483
540
  if (this.mode === 'provider') return this.getFilteredProviders().length;
541
+ if (this.mode === 'embeddingProvider') return this.embeddingProviders.length;
484
542
  return this.effortLevels.length;
485
543
  }
486
544