@pellux/goodvibes-tui 1.1.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +20 -11
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/core/alert-gating.ts +67 -0
  6. package/src/core/approval-alert.ts +72 -0
  7. package/src/core/budget-breach-notifier.ts +106 -0
  8. package/src/core/conversation-rendering.ts +19 -0
  9. package/src/core/conversation.ts +18 -0
  10. package/src/core/focus-tracker.ts +41 -0
  11. package/src/core/long-task-notifier.ts +33 -6
  12. package/src/core/system-message-router.ts +37 -51
  13. package/src/core/turn-cancellation.ts +20 -0
  14. package/src/core/turn-event-wiring.ts +64 -3
  15. package/src/export/cost-utils.ts +36 -0
  16. package/src/input/command-registry.ts +38 -1
  17. package/src/input/commands/checkpoint-runtime.ts +280 -0
  18. package/src/input/commands/codebase-runtime.ts +192 -0
  19. package/src/input/commands/eval.ts +1 -1
  20. package/src/input/commands/health-runtime.ts +1 -1
  21. package/src/input/commands/image-runtime.ts +112 -0
  22. package/src/input/commands/intelligence-runtime.ts +11 -1
  23. package/src/input/commands/local-auth-runtime.ts +4 -1
  24. package/src/input/commands/local-runtime.ts +9 -3
  25. package/src/input/commands/marketplace-runtime.ts +2 -2
  26. package/src/input/commands/memory.ts +6 -1
  27. package/src/input/commands/operator-panel-runtime.ts +40 -24
  28. package/src/input/commands/planning-runtime.ts +26 -3
  29. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  30. package/src/input/commands/plugin-runtime.ts +2 -2
  31. package/src/input/commands/policy-dispatch.ts +21 -0
  32. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  33. package/src/input/commands/qrcode-runtime.ts +3 -3
  34. package/src/input/commands/recall-review.ts +35 -0
  35. package/src/input/commands/remote-runtime.ts +3 -3
  36. package/src/input/commands/runtime-services.ts +54 -13
  37. package/src/input/commands/services-runtime.ts +1 -1
  38. package/src/input/commands/session-workflow.ts +16 -1
  39. package/src/input/commands/settings-sync-runtime.ts +1 -1
  40. package/src/input/commands/shell-core.ts +15 -7
  41. package/src/input/commands/skills-runtime.ts +2 -8
  42. package/src/input/commands/subscription-runtime.ts +2 -2
  43. package/src/input/commands/test-runtime.ts +277 -0
  44. package/src/input/commands/websearch-runtime.ts +101 -0
  45. package/src/input/commands/work-plan-runtime.ts +36 -10
  46. package/src/input/commands/workstream-runtime.ts +338 -0
  47. package/src/input/commands.ts +12 -0
  48. package/src/input/config-modal-types.ts +161 -0
  49. package/src/input/config-modal.ts +377 -0
  50. package/src/input/feed-context-factory.ts +7 -8
  51. package/src/input/handler-command-route.ts +27 -17
  52. package/src/input/handler-feed-routes.ts +61 -23
  53. package/src/input/handler-feed.ts +47 -23
  54. package/src/input/handler-interactions.ts +1 -3
  55. package/src/input/handler-modal-routes.ts +65 -0
  56. package/src/input/handler-modal-stack.ts +3 -5
  57. package/src/input/handler-modal-token-routes.ts +16 -49
  58. package/src/input/handler-picker-routes.ts +11 -94
  59. package/src/input/handler-shortcuts.ts +24 -14
  60. package/src/input/handler-types.ts +2 -6
  61. package/src/input/handler-ui-state.ts +10 -22
  62. package/src/input/handler.ts +6 -28
  63. package/src/input/keybindings.ts +22 -8
  64. package/src/input/model-picker-types.ts +24 -6
  65. package/src/input/model-picker.ts +58 -0
  66. package/src/input/panel-integration-actions.ts +9 -170
  67. package/src/input/settings-modal-data.ts +95 -0
  68. package/src/input/settings-modal-mutations.ts +6 -4
  69. package/src/main.ts +24 -27
  70. package/src/panels/agent-inspector-shared.ts +2 -1
  71. package/src/panels/base-panel.ts +22 -1
  72. package/src/panels/builtin/agent.ts +21 -107
  73. package/src/panels/builtin/development.ts +15 -61
  74. package/src/panels/builtin/knowledge.ts +8 -32
  75. package/src/panels/builtin/operations.ts +84 -428
  76. package/src/panels/builtin/session.ts +21 -112
  77. package/src/panels/builtin/shared.ts +9 -43
  78. package/src/panels/builtin-modals.ts +218 -0
  79. package/src/panels/builtin-panels.ts +5 -0
  80. package/src/panels/cost-tracker-panel.ts +36 -10
  81. package/src/panels/diff-panel.ts +12 -43
  82. package/src/panels/eval-registry.ts +60 -0
  83. package/src/panels/fleet-panel.ts +800 -0
  84. package/src/panels/fleet-read-model.ts +477 -0
  85. package/src/panels/fleet-steer.ts +92 -0
  86. package/src/panels/fleet-stop.ts +125 -0
  87. package/src/panels/fleet-tabs.ts +230 -0
  88. package/src/panels/fleet-transcript.ts +356 -0
  89. package/src/panels/index.ts +7 -31
  90. package/src/panels/modals/hooks-modal.ts +187 -0
  91. package/src/panels/modals/keybindings-modal.ts +151 -0
  92. package/src/panels/modals/knowledge-modal.ts +158 -0
  93. package/src/panels/modals/local-auth-modal.ts +132 -0
  94. package/src/panels/modals/marketplace-modal.ts +218 -0
  95. package/src/panels/modals/memory-modal.ts +180 -0
  96. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  97. package/src/panels/modals/modal-theme.ts +36 -0
  98. package/src/panels/modals/pairing-modal.ts +144 -0
  99. package/src/panels/modals/planning-modal.ts +282 -0
  100. package/src/panels/modals/plugins-modal.ts +174 -0
  101. package/src/panels/modals/policy-modal.ts +256 -0
  102. package/src/panels/modals/provider-health-modal.ts +136 -0
  103. package/src/panels/modals/remote-modal.ts +115 -0
  104. package/src/panels/modals/sandbox-modal.ts +150 -0
  105. package/src/panels/modals/security-modal.ts +217 -0
  106. package/src/panels/modals/services-modal.ts +179 -0
  107. package/src/panels/modals/settings-sync-modal.ts +142 -0
  108. package/src/panels/modals/skills-modal.ts +189 -0
  109. package/src/panels/modals/subscription-modal.ts +172 -0
  110. package/src/panels/modals/work-plan-modal.ts +149 -0
  111. package/src/panels/panel-confirm-overlay.ts +72 -0
  112. package/src/panels/panel-manager.ts +108 -5
  113. package/src/panels/project-planning-answer-actions.ts +4 -2
  114. package/src/panels/skills-panel.ts +7 -51
  115. package/src/panels/types.ts +13 -0
  116. package/src/permissions/hunk-selection.ts +179 -0
  117. package/src/permissions/prompt.ts +60 -4
  118. package/src/renderer/compaction-history-modal.ts +19 -3
  119. package/src/renderer/compaction-preview.ts +13 -2
  120. package/src/renderer/compaction-quality.ts +100 -0
  121. package/src/renderer/config-modal.ts +81 -0
  122. package/src/renderer/conversation-overlays.ts +10 -17
  123. package/src/renderer/fleet-tab-strip.ts +56 -0
  124. package/src/renderer/footer-tips.ts +10 -2
  125. package/src/renderer/git-status.ts +40 -0
  126. package/src/renderer/help-overlay.ts +2 -2
  127. package/src/renderer/model-workspace.ts +44 -1
  128. package/src/renderer/panel-workspace-bar.ts +8 -2
  129. package/src/renderer/turn-injection.ts +114 -0
  130. package/src/runtime/bootstrap-command-context.ts +21 -1
  131. package/src/runtime/bootstrap-command-parts.ts +34 -7
  132. package/src/runtime/bootstrap-core.ts +14 -4
  133. package/src/runtime/bootstrap-shell.ts +29 -16
  134. package/src/runtime/bootstrap.ts +11 -17
  135. package/src/runtime/code-index-services.ts +112 -0
  136. package/src/runtime/orchestrator-core-services.ts +49 -0
  137. package/src/runtime/process-lifecycle.ts +3 -1
  138. package/src/runtime/services.ts +91 -43
  139. package/src/runtime/ui-services.ts +8 -0
  140. package/src/runtime/workstream-services.ts +195 -0
  141. package/src/shell/blocking-input.ts +22 -1
  142. package/src/shell/ui-openers.ts +129 -17
  143. package/src/utils/format-duration.ts +8 -18
  144. package/src/utils/splash-lines.ts +1 -1
  145. package/src/version.ts +1 -1
  146. package/src/panels/agent-inspector-panel.ts +0 -786
  147. package/src/panels/approval-panel.ts +0 -252
  148. package/src/panels/automation-control-panel.ts +0 -479
  149. package/src/panels/cockpit-panel.ts +0 -481
  150. package/src/panels/cockpit-read-model.ts +0 -238
  151. package/src/panels/communication-panel.ts +0 -256
  152. package/src/panels/control-plane-panel.ts +0 -470
  153. package/src/panels/debug-panel.ts +0 -615
  154. package/src/panels/docs-panel.ts +0 -384
  155. package/src/panels/eval-panel.ts +0 -627
  156. package/src/panels/file-explorer-panel.ts +0 -673
  157. package/src/panels/file-preview-panel.ts +0 -517
  158. package/src/panels/hooks-panel.ts +0 -401
  159. package/src/panels/incident-review-panel.ts +0 -406
  160. package/src/panels/intelligence-panel.ts +0 -383
  161. package/src/panels/knowledge-graph-panel.ts +0 -515
  162. package/src/panels/marketplace-panel.ts +0 -399
  163. package/src/panels/memory-panel.ts +0 -558
  164. package/src/panels/ops-control-panel.ts +0 -329
  165. package/src/panels/ops-strategy-panel.ts +0 -321
  166. package/src/panels/orchestration-panel.ts +0 -465
  167. package/src/panels/panel-list-panel.ts +0 -566
  168. package/src/panels/plan-dashboard-panel.ts +0 -707
  169. package/src/panels/policy-panel.ts +0 -517
  170. package/src/panels/project-planning-panel.ts +0 -731
  171. package/src/panels/provider-health-panel.ts +0 -678
  172. package/src/panels/provider-health-tracker.ts +0 -310
  173. package/src/panels/provider-health-views.ts +0 -567
  174. package/src/panels/qr-panel.ts +0 -280
  175. package/src/panels/remote-panel.ts +0 -534
  176. package/src/panels/routes-panel.ts +0 -241
  177. package/src/panels/sandbox-panel.ts +0 -456
  178. package/src/panels/security-panel.ts +0 -447
  179. package/src/panels/services-panel.ts +0 -329
  180. package/src/panels/session-browser-panel.ts +0 -496
  181. package/src/panels/settings-sync-panel.ts +0 -398
  182. package/src/panels/subscription-panel.ts +0 -342
  183. package/src/panels/symbol-outline-panel.ts +0 -619
  184. package/src/panels/system-messages-panel.ts +0 -364
  185. package/src/panels/tasks-panel.ts +0 -608
  186. package/src/panels/thinking-panel.ts +0 -333
  187. package/src/panels/tool-inspector-panel.ts +0 -537
  188. package/src/panels/work-plan-panel.ts +0 -540
  189. package/src/panels/worktree-panel.ts +0 -360
  190. package/src/panels/wrfc-panel.ts +0 -790
  191. package/src/renderer/agent-detail-modal.ts +0 -466
  192. package/src/renderer/live-tail-modal.ts +0 -156
  193. package/src/renderer/process-modal.ts +0 -671
  194. package/src/renderer/qr-renderer.ts +0 -120
@@ -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';
@@ -184,13 +180,11 @@ export class InputHandler implements InputHandlerLike {
184
180
  public modelPicker: ModelPickerModal;
185
181
  public selectionModal = new SelectionModal();
186
182
  public searchManager = new SearchManager();
187
- public processModal: ProcessModal;
188
- public liveTailModal: LiveTailModal;
189
- public agentDetailModal: AgentDetailModal;
190
183
  public contextInspectorModal = new ContextInspectorModal();
191
184
  public bookmarkModal: BookmarkModal;
192
185
  public blockActionsMenu = new BlockActionsMenu();
193
186
  public settingsModal = new SettingsModal();
187
+ public configModal = new ConfigModal();
194
188
  public mcpWorkspace = new McpWorkspace();
195
189
  public onboardingWizard = new OnboardingWizardController();
196
190
  public onboardingModelPickerCancelSnapshot: OnboardingWizardSnapshot | null = null;
@@ -272,23 +266,8 @@ export class InputHandler implements InputHandlerLike {
272
266
  uiServices.providers.benchmarkStore,
273
267
  uiServices.providers.providerRegistry,
274
268
  );
275
- this.processModal = new ProcessModal({
276
- agentManager: uiServices.agents.agentManager,
277
- processManager: uiServices.shell.processManager,
278
- wrfcController: uiServices.agents.wrfcController,
279
- });
280
- this.liveTailModal = new LiveTailModal({
281
- agentManager: uiServices.agents.agentManager,
282
- processManager: uiServices.shell.processManager,
283
- });
284
- this.agentDetailModal = new AgentDetailModal({
285
- agentManager: uiServices.agents.agentManager,
286
- agentMessageBus: uiServices.agents.agentMessageBus,
287
- sessionLogPathResolver: (agentId) => uiServices.environment.shellPaths.resolveProjectPath('tui', 'sessions', `${agentId}.jsonl`),
288
- // SDK 0.23.0: supply wrfcController so the modal can show constraint data
289
- wrfcController: uiServices.agents.wrfcController,
290
- cancelAgent: (agentId: string) => uiServices.agents.agentManager.cancel(agentId),
291
- });
269
+ // W6.1 retirement: ProcessModal/LiveTailModal/AgentDetailModal were removed
270
+ // — F2 now opens the Fleet panel, which subsumes the live process tree.
292
271
  this.bookmarkModal = new BookmarkModal(uiServices.shell.bookmarkManager);
293
272
  this.sessionPickerModal = new SessionPickerModal(uiServices.sessions.sessionManager);
294
273
  this.profilePickerModal = new ProfilePickerModal(uiServices.shell.profileManager);
@@ -322,6 +301,7 @@ export class InputHandler implements InputHandlerLike {
322
301
  mcpWorkspace: this.mcpWorkspace,
323
302
  sessionPickerModal: this.sessionPickerModal,
324
303
  profilePickerModal: this.profilePickerModal,
304
+ configModal: this.configModal,
325
305
  historySearch: this.historySearch,
326
306
  commandRegistry: this.commandRegistry,
327
307
  commandContext: this.commandContext,
@@ -329,9 +309,6 @@ export class InputHandler implements InputHandlerLike {
329
309
  filePicker: this.filePicker,
330
310
  modelPicker: this.modelPicker,
331
311
  onboardingWizard: this.onboardingWizard,
332
- processModal: this.processModal,
333
- liveTailModal: this.liveTailModal,
334
- agentDetailModal: this.agentDetailModal,
335
312
  contextInspectorModal: this.contextInspectorModal,
336
313
  blockActionsMenu: this.blockActionsMenu,
337
314
  searchManager: this.searchManager,
@@ -341,6 +318,7 @@ export class InputHandler implements InputHandlerLike {
341
318
  panelManager: this.uiServices.shell.panelManager,
342
319
  keybindingsManager: this.uiServices.shell.keybindingsManager,
343
320
  killRing: this.killRing,
321
+ focusTracker: this.uiServices.platform.focusTracker,
344
322
  getHistory: this.getHistory,
345
323
  getViewportHeight: this.getViewportHeight,
346
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
 
@@ -1,64 +1,16 @@
1
1
  import type { CommandContext } from './command-registry.ts';
2
- import { logger } from '@pellux/goodvibes-sdk/platform/utils';
3
2
  import type { Panel } from '../panels/types.ts';
4
3
  import type { PanelManager } from '../panels/panel-manager.ts';
5
- import { FileExplorerPanel } from '../panels/file-explorer-panel.ts';
6
- import { FilePreviewPanel } from '../panels/file-preview-panel.ts';
7
- import { SymbolOutlinePanel } from '../panels/symbol-outline-panel.ts';
8
- import { ApprovalPanel } from '../panels/approval-panel.ts';
9
- import { TasksPanel } from '../panels/tasks-panel.ts';
10
- import { OrchestrationPanel } from '../panels/orchestration-panel.ts';
11
- import { AgentInspectorPanel } from '../panels/agent-inspector-panel.ts';
12
- import { DiffPanel } from '../panels/diff-panel.ts';
13
-
14
- function ensurePreviewPanel(panelManager: PanelManager): FilePreviewPanel | null {
15
- const existing = panelManager.getPanel('preview');
16
- if (existing instanceof FilePreviewPanel) {
17
- const pane = panelManager.getPaneOf('preview');
18
- panelManager.activateById('preview');
19
- if (pane) panelManager.focusPane(pane);
20
- return existing;
21
- }
22
- const targetPane: 'top' | 'bottom' = panelManager.isBottomPaneVisible()
23
- ? (panelManager.getFocusedPane() === 'top' ? 'bottom' : 'top')
24
- : 'bottom';
25
- const opened = panelManager.open('preview', targetPane);
26
- panelManager.show();
27
- panelManager.focusPane(targetPane);
28
- return opened instanceof FilePreviewPanel ? opened : null;
29
- }
30
-
31
- // WO-133: shared by explorer's and preview's 'd' (diff) key — same
32
- // open/focus bridge as ensurePreviewPanel above.
33
- function ensureDiffPanel(panelManager: PanelManager): DiffPanel | null {
34
- const existing = panelManager.getPanel('diff');
35
- if (existing instanceof DiffPanel) {
36
- const pane = panelManager.getPaneOf('diff');
37
- panelManager.activateById('diff');
38
- if (pane) panelManager.focusPane(pane);
39
- return existing;
40
- }
41
- const targetPane: 'top' | 'bottom' = panelManager.isBottomPaneVisible()
42
- ? (panelManager.getFocusedPane() === 'top' ? 'bottom' : 'top')
43
- : 'bottom';
44
- const opened = panelManager.open('diff', targetPane);
45
- panelManager.show();
46
- panelManager.focusPane(targetPane);
47
- return opened instanceof DiffPanel ? opened : null;
48
- }
49
-
50
- // Exported so a future preview-reload action (e.g. an explicit "r" reload
51
- // key) can re-sync the outline against the same file without duplicating
52
- // this lookup — the panel-integration wiring for that key lands separately.
53
- export function syncSymbolOutlineFromPreview(panelManager: PanelManager, previewPanel: FilePreviewPanel): void {
54
- const symbols = panelManager.getPanel('symbols');
55
- const filePath = previewPanel.getCurrentFilePath();
56
- const source = previewPanel.getSource();
57
- if (symbols instanceof SymbolOutlinePanel && filePath && source !== null) {
58
- symbols.loadFile(filePath, source);
59
- }
60
- }
61
4
 
5
+ // W6.1 (the purge): every instanceof-routed branch this file used to carry
6
+ // (FileExplorerPanel/FilePreviewPanel/SymbolOutlinePanel — DELETE;
7
+ // ApprovalPanel/TasksPanel/OrchestrationPanel/AgentInspectorPanel —
8
+ // RETIRE-INTO-FLEET) targeted a panel class that no longer exists. Panels
9
+ // migrated onto the formal `Panel.handlePanelIntegrationAction` hook (the
10
+ // preferred seam — see the comment below) are unaffected; this function now
11
+ // only provides that passthrough. See
12
+ // .goodvibes/audit/2026-07-04-wave6-briefs.json (W6.1) for the disposition
13
+ // map.
62
14
  export function handlePanelIntegrationAction(
63
15
  panelManager: PanelManager,
64
16
  activePanel: Panel | null,
@@ -77,118 +29,5 @@ export function handlePanelIntegrationAction(
77
29
  if (consumed) return true;
78
30
  }
79
31
 
80
- if ((key === 'enter' || key === 'return' || key === 'right') && activePanel instanceof FileExplorerPanel) {
81
- const filePath = activePanel.getFocusedFilePath();
82
- if (!filePath) return false;
83
- const previewPanel = ensurePreviewPanel(panelManager);
84
- if (!previewPanel) return false;
85
- previewPanel.openFile(filePath);
86
- syncSymbolOutlineFromPreview(panelManager, previewPanel);
87
- return true;
88
- }
89
-
90
- // WO-133: 'd' on the explorer diffs the currently focused file, and 'd' on
91
- // the preview diffs whatever file is currently open — both against HEAD,
92
- // both via the same DiffPanel.showFileDiffs entry point.
93
- if (key === 'd' && activePanel instanceof FileExplorerPanel) {
94
- const filePath = activePanel.getFocusedFilePath();
95
- if (!filePath) return false;
96
- const diffPanel = ensureDiffPanel(panelManager);
97
- if (!diffPanel) return false;
98
- void diffPanel.showFileDiffs([filePath], 'HEAD').catch((err) => {
99
- logger.debug('explorer diff dispatch failed', { err });
100
- });
101
- return true;
102
- }
103
-
104
- if (key === 'd' && activePanel instanceof FilePreviewPanel) {
105
- const filePath = activePanel.getCurrentFilePath();
106
- if (!filePath) return false;
107
- const diffPanel = ensureDiffPanel(panelManager);
108
- if (!diffPanel) return false;
109
- void diffPanel.showFileDiffs([filePath], 'HEAD').catch((err) => {
110
- logger.debug('preview diff dispatch failed', { err });
111
- });
112
- return true;
113
- }
114
-
115
- // WO-133: 'r' on the preview reloads the open file from disk, then
116
- // re-syncs the symbol outline against the refreshed content (WO-126's
117
- // async tree-sitter loadFile via syncSymbolOutlineFromPreview above) once
118
- // the reload actually settles.
119
- if (key === 'r' && activePanel instanceof FilePreviewPanel) {
120
- const reloaded = activePanel.consumePendingReload();
121
- if (!reloaded) return false;
122
- void reloaded.then(() => syncSymbolOutlineFromPreview(panelManager, activePanel)).catch((err) => {
123
- logger.debug('preview reload dispatch failed', { err });
124
- });
125
- return true;
126
- }
127
-
128
- if ((key === 'enter' || key === 'return') && activePanel instanceof SymbolOutlinePanel) {
129
- const location = activePanel.getSelectedLocation();
130
- if (!location) return false;
131
- const previewPanel = ensurePreviewPanel(panelManager);
132
- if (!previewPanel) return false;
133
- if (previewPanel.getCurrentFilePath() !== location.path) {
134
- previewPanel.openFile(location.path);
135
- syncSymbolOutlineFromPreview(panelManager, previewPanel);
136
- }
137
- previewPanel.goToLine(location.line);
138
- return true;
139
- }
140
-
141
- if ((key === 'enter' || key === 'return') && activePanel instanceof ApprovalPanel) {
142
- const command = activePanel.getSelectedCommand();
143
- if (!command || !commandContext?.executeCommand) return false;
144
- const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
145
- const [name, ...args] = parts;
146
- if (!name) return false;
147
- void commandContext.executeCommand(name, args).catch((err) => { logger.debug('approval panel command dispatch failed', { err }); });
148
- return true;
149
- }
150
-
151
- // WO-131: Enter on a Tasks row — agent-kind tasks jump straight to the Agent
152
- // Inspector (which owns the deep per-agent timeline); everything else's
153
- // advertised worktree follow-up is dispatched for real via ctx.executeCommand
154
- // instead of being printed as a static "/worktree task <task-id>" signpost.
155
- // w dispatches the task-family posture review the header line advertises.
156
- if ((key === 'enter' || key === 'return' || key === 'w') && activePanel instanceof TasksPanel) {
157
- const followUp = activePanel.consumePendingFollowUp();
158
- if (!followUp) return false;
159
- if (followUp.kind === 'agent-jump') {
160
- const inspector = panelManager.open('inspector');
161
- if (!(inspector instanceof AgentInspectorPanel)) return false;
162
- inspector.inspectAgent(followUp.agentId);
163
- return true;
164
- }
165
- if (!commandContext?.executeCommand) return false;
166
- if (followUp.kind === 'teamwork-review') {
167
- void commandContext.executeCommand('teamwork', ['review']).catch((err) => {
168
- logger.debug('tasks panel teamwork review dispatch failed', { err });
169
- });
170
- return true;
171
- }
172
- void commandContext.executeCommand('worktree', ['task', followUp.taskId]).catch((err) => {
173
- logger.debug('tasks panel worktree review dispatch failed', { err });
174
- });
175
- return true;
176
- }
177
-
178
- // WO-131: Enter on a node-focused Orchestration row jumps to the Agent
179
- // Inspector (agent-backed nodes) or the Tasks panel (task-backed nodes).
180
- if ((key === 'enter' || key === 'return') && activePanel instanceof OrchestrationPanel) {
181
- const jump = activePanel.consumePendingNodeJump();
182
- if (!jump) return false;
183
- if (jump.kind === 'agent-jump') {
184
- const inspector = panelManager.open('inspector');
185
- if (!(inspector instanceof AgentInspectorPanel)) return false;
186
- inspector.inspectAgent(jump.id);
187
- return true;
188
- }
189
- panelManager.open('tasks');
190
- return true;
191
- }
192
-
193
32
  return false;
194
33
  }