@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
@@ -1,375 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // DocsPanel — tool list, model capabilities, and keyboard shortcut reference.
3
- // ---------------------------------------------------------------------------
4
-
5
- import type { Line } from '../types/grid.ts';
6
- import { BasePanel } from './base-panel.ts';
7
- import { buildKeyboardHints, buildPanelLine, buildPanelWorkspace, buildSearchInputLine, resolveScrollablePanelSection, extendPalette, DEFAULT_PANEL_PALETTE } from './polish.ts';
8
- import type { ToolCatalogQuery } from '../runtime/ui-service-queries.ts';
9
- import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
10
- import type { KeybindingsManager } from '../input/keybindings.ts';
11
- import type { PanelIntegrationContext } from './types.ts';
12
- import { ToolInspectorPanel } from './tool-inspector-panel.ts';
13
- import { isPanelSearchBackspace, isPanelSearchPrintable } from './search-focus.ts';
14
-
15
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
16
- // Panel-specific domain colors with no clean shared equivalent.
17
- sectionFg: '#00ffff',
18
- toolFg: '#88ccff',
19
- selected: '#00ffff',
20
- });
21
-
22
- /**
23
- * Minimal provider-registry surface DocsPanel needs: list models, read the
24
- * live active model (to mark it in the list), and switch models (Enter on a
25
- * model row is a real in-panel action, not a printed signpost). Structurally
26
- * satisfied by the full ProviderRegistry passed in from bootstrap.
27
- */
28
- interface DocsProviderRegistry {
29
- listModels(): ModelDefinition[];
30
- getCurrentModel?(): ModelDefinition | undefined;
31
- setCurrentModel?(registryKey: string): void;
32
- }
33
-
34
- type DocSection = 'tools' | 'models' | 'shortcuts';
35
-
36
- interface FlatRow {
37
- kind: 'header' | 'item' | 'detail' | 'empty';
38
- text: string;
39
- fg: string;
40
- bg: string;
41
- bold?: boolean;
42
- /** Present on tool 'item' rows — the tool name Enter should filter the inspector to. */
43
- toolName?: string;
44
- /** Present on model 'item' rows — the model's registryKey (provider:id) for setCurrentModel. */
45
- modelKey?: string;
46
- /** Present on model 'item' rows — whether Enter is allowed to switch to this model. */
47
- modelSelectable?: boolean;
48
- }
49
-
50
- function renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
51
- const bg = isCursor ? C.selectBg : row.bg;
52
- return buildPanelLine(width, [
53
- [isCursor ? '▸' : ' ', C.selected, bg],
54
- [row.text, isCursor ? C.selected : row.fg, bg],
55
- ]);
56
- }
57
-
58
- export class DocsPanel extends BasePanel {
59
- private toolRegistry: ToolCatalogQuery | null = null;
60
- private providerRegistry: DocsProviderRegistry | null = null;
61
- private keybindingsManager: KeybindingsManager | null = null;
62
- private section: DocSection = 'tools';
63
- private searchQuery = '';
64
- private searching = false;
65
- private rows: FlatRow[] = [];
66
- private cursorIndex = 0;
67
- private scrollOffset = 0;
68
- /** Set by handleInput('enter') on a tool row; consumed by handlePanelIntegrationAction, which has the PanelManager reference needed to open the sibling inspector. */
69
- private _pendingToolJump: string | null = null;
70
-
71
- constructor(toolRegistry?: ToolCatalogQuery, providerRegistry?: DocsProviderRegistry, keybindingsManager?: KeybindingsManager) {
72
- super('docs', 'Docs', '?', 'session');
73
- this.toolRegistry = toolRegistry ?? null;
74
- this.providerRegistry = providerRegistry ?? null;
75
- this.keybindingsManager = keybindingsManager ?? null;
76
- }
77
-
78
- override onActivate(): void {
79
- this.needsRender = true;
80
- this._buildRows();
81
- }
82
-
83
- /**
84
- * WO-153: converged modal '/' filter (mirrors ScrollableListPanel's
85
- * _handleFilterKey). Returns `true`/`false` when consumed/ignored in
86
- * filter context, or `null` to fall through to section hotkeys/navigation.
87
- */
88
- private _handleSearchKey(key: string): boolean | null {
89
- if (this.searching) {
90
- if (key === 'escape') {
91
- this.searching = false;
92
- this.searchQuery = '';
93
- this._buildRows();
94
- return true;
95
- }
96
- if (key === 'return' || key === 'enter') {
97
- this.searching = false; // commit; keep the query applied
98
- this._buildRows();
99
- return true;
100
- }
101
- if (isPanelSearchBackspace(key)) {
102
- this.searchQuery = this.searchQuery.slice(0, -1);
103
- this._buildRows();
104
- return true;
105
- }
106
- // Arrow/paging keys navigate the filtered rows — fall through.
107
- if (key === 'up' || key === 'down' || key === 'pageup' || key === 'pagedown') {
108
- return null;
109
- }
110
- // Any printable character (including section hotkeys like t/m/k) extends the query.
111
- if (isPanelSearchPrintable(key)) {
112
- this.searchQuery += key;
113
- this._buildRows();
114
- return true;
115
- }
116
- return false;
117
- }
118
- if (key === '/') {
119
- this._startSearch();
120
- return true;
121
- }
122
- return null;
123
- }
124
-
125
- handleInput(key: string): boolean {
126
- const searchResult = this._handleSearchKey(key);
127
- if (searchResult !== null) return searchResult;
128
-
129
- switch (key) {
130
- case 'up': this._move(-1); return true;
131
- case 'down': this._move(1); return true;
132
- case 'pageup': this._move(-10); return true;
133
- case 'pagedown': this._move(10); return true;
134
- case 't': this._setSection('tools'); return true;
135
- case 'm': this._setSection('models'); return true;
136
- case 'k': this._setSection('shortcuts'); return true;
137
- case 'enter':
138
- case 'return': return this._activateSelected();
139
- default: return false;
140
- }
141
- }
142
-
143
- /**
144
- * Enter on the cursor row. Tool rows can't act directly — opening the tool
145
- * inspector needs the PanelManager, which only handlePanelIntegrationAction
146
- * has — so this just records intent and lets that hook finish the jump.
147
- * Model rows need no cross-panel access: switching the active model is a
148
- * direct providerRegistry call, so it happens right here.
149
- */
150
- private _activateSelected(): boolean {
151
- const row = this.rows[this.cursorIndex];
152
- if (!row || row.kind !== 'item') return false;
153
- if (this.section === 'tools' && row.toolName) {
154
- this._pendingToolJump = row.toolName;
155
- return true;
156
- }
157
- if (this.section === 'models' && row.modelKey) {
158
- if (row.modelSelectable && this.providerRegistry?.setCurrentModel) {
159
- this.providerRegistry.setCurrentModel(row.modelKey);
160
- this._buildRows();
161
- }
162
- return true;
163
- }
164
- return false;
165
- }
166
-
167
- /**
168
- * Cross-panel integration hook — opens ToolInspectorPanel filtered to the
169
- * tool selected on Enter (session.ts:102 sibling-open pattern), consuming
170
- * the intent recorded by _activateSelected.
171
- */
172
- handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
173
- if (!this._pendingToolJump) return false;
174
- const tool = this._pendingToolJump;
175
- this._pendingToolJump = null;
176
- const inspector = ctx.panelManager.open('tools');
177
- if (!(inspector instanceof ToolInspectorPanel)) return false;
178
- inspector.filterByTool(tool);
179
- return true;
180
- }
181
-
182
- render(width: number, height: number): Line[] {
183
- if (height <= 0 || width <= 0) return [];
184
- const sectionLabel = this.section === 'tools' ? 'Tools' : this.section === 'models' ? 'Models' : 'Shortcuts';
185
- this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, Math.max(0, this.rows.length - 1)));
186
- const controlsSection = {
187
- title: 'Controls',
188
- lines: [
189
- buildPanelLine(width, [
190
- [' t', DEFAULT_PANEL_PALETTE.info], [' tools', DEFAULT_PANEL_PALETTE.dim],
191
- [' m', DEFAULT_PANEL_PALETTE.info], [' models', DEFAULT_PANEL_PALETTE.dim],
192
- [' k', DEFAULT_PANEL_PALETTE.info], [' shortcuts', DEFAULT_PANEL_PALETTE.dim],
193
- [' /', DEFAULT_PANEL_PALETTE.info], [' filter', DEFAULT_PANEL_PALETTE.dim],
194
- ]),
195
- this._buildFilterLine(width),
196
- ],
197
- } as const;
198
- // Context-aware footer: while filtering, surface only the keys that work in
199
- // the filter field; otherwise surface section + navigation keys.
200
- const footerLines = [this.searching
201
- ? buildKeyboardHints(width, [
202
- { keys: 'type', label: 'filter' },
203
- { keys: 'Enter', label: 'apply' },
204
- { keys: 'Esc', label: 'clear' },
205
- ], DEFAULT_PANEL_PALETTE)
206
- : buildKeyboardHints(width, [
207
- { keys: 't/m/k', label: 'tools / models / shortcuts' },
208
- { keys: '↑/↓', label: 'navigate' },
209
- ...(this.section === 'tools' ? [{ keys: 'Enter', label: 'open in tool inspector' }] : []),
210
- ...(this.section === 'models' ? [{ keys: 'Enter', label: 'set active model' }] : []),
211
- { keys: '/', label: 'filter' },
212
- ], DEFAULT_PANEL_PALETTE)];
213
-
214
- const sectionWindow = resolveScrollablePanelSection(width, height, {
215
- intro: 'Browse built-in tool docs, available models, and keyboard shortcuts from one shared reference surface.',
216
- palette: DEFAULT_PANEL_PALETTE,
217
- footerLines,
218
- beforeSections: [controlsSection],
219
- section: {
220
- title: sectionLabel,
221
- scrollableLines: this.rows.map((row, absIdx) => {
222
- const isCursor = absIdx === this.cursorIndex && row.kind !== 'header' && row.kind !== 'empty';
223
- return renderRow(width, row, isCursor);
224
- }),
225
- selectedIndex: this.cursorIndex,
226
- scrollOffset: this.scrollOffset,
227
- minRows: 8,
228
- },
229
- });
230
- this.scrollOffset = sectionWindow.scrollOffset;
231
-
232
- return buildPanelWorkspace(width, height, {
233
- title: ` Docs / ${sectionLabel}`,
234
- intro: 'Browse built-in tool docs, available models, and keyboard shortcuts from one shared reference surface.',
235
- sections: [
236
- controlsSection,
237
- sectionWindow.section.lines.length > 0 ? sectionWindow.section : { title: sectionLabel, lines: [buildPanelLine(width, [[' No matching docs', DEFAULT_PANEL_PALETTE.dim]])] },
238
- ],
239
- footerLines,
240
- palette: DEFAULT_PANEL_PALETTE,
241
- });
242
- }
243
-
244
- private _setSection(section: DocSection): void {
245
- this.section = section;
246
- this.searchQuery = '';
247
- this.cursorIndex = 0;
248
- this.scrollOffset = 0;
249
- this._buildRows();
250
- }
251
-
252
- private _startSearch(): void {
253
- this.searching = true;
254
- this.markDirty();
255
- }
256
-
257
- /**
258
- * The filter input line — pinned rendering contract shared with
259
- * ScrollableListPanel.buildFilterLine: 'Filter: ' unfocused / '[Filter] '
260
- * focused, literal trailing '_' cursor while active (active:false is
261
- * passed to buildSearchInputLine to suppress its block-glyph cursor
262
- * substitution).
263
- */
264
- private _buildFilterLine(width: number): Line {
265
- const label = this.searching ? '[Filter] ' : 'Filter: ';
266
- const value = this.searching ? `${this.searchQuery}_` : this.searchQuery;
267
- return buildSearchInputLine(width, label, value, DEFAULT_PANEL_PALETTE, {
268
- active: false,
269
- bg: this.searching ? DEFAULT_PANEL_PALETTE.inputBg : DEFAULT_PANEL_PALETTE.sectionBg,
270
- emptyLabel: '(/ to filter)',
271
- valueColor: this.searching ? DEFAULT_PANEL_PALETTE.info : (this.searchQuery ? DEFAULT_PANEL_PALETTE.value : DEFAULT_PANEL_PALETTE.dim),
272
- });
273
- }
274
-
275
- private _move(delta: number): void {
276
- if (this.rows.length === 0) return;
277
- this.cursorIndex = Math.max(0, Math.min(this.rows.length - 1, this.cursorIndex + delta));
278
- this.markDirty();
279
- }
280
-
281
- private _buildRows(): void {
282
- const q = this.searchQuery.trim().toLowerCase();
283
- const rows: FlatRow[] = [];
284
-
285
- if (this.section === 'tools') {
286
- const tools = this.toolRegistry?.list() ?? [];
287
- const filtered = q ? tools.filter(t => t.definition.name.toLowerCase().includes(q) || (t.definition.description ?? '').toLowerCase().includes(q)) : tools;
288
- if (filtered.length === 0) {
289
- rows.push({ kind: 'empty', text: q ? ` No tools match "${this.searchQuery}". Press Esc to clear the filter.` : ' Tool registry not wired into this session.', fg: C.dim, bg: '' });
290
- } else {
291
- rows.push({ kind: 'header', text: ` Tools (${filtered.length})`, fg: C.sectionFg, bg: C.sectionBg, bold: true });
292
- for (const tool of filtered) {
293
- rows.push({ kind: 'item', text: ` ${tool.definition.name}`, fg: C.toolFg, bg: '', bold: true, toolName: tool.definition.name });
294
- if (tool.definition.description) {
295
- rows.push({ kind: 'detail', text: ` ${tool.definition.description}`, fg: C.label, bg: '' });
296
- }
297
- const metadata: string[] = [];
298
- if (tool.definition.sideEffects && tool.definition.sideEffects.length > 0) {
299
- metadata.push(`effects: ${tool.definition.sideEffects.join(', ')}`);
300
- }
301
- if (tool.definition.concurrency) {
302
- metadata.push(`concurrency: ${tool.definition.concurrency}`);
303
- }
304
- if (tool.definition.supportsProgress) {
305
- metadata.push('progress');
306
- }
307
- if (tool.definition.supportsStreamingOutput) {
308
- metadata.push('streaming');
309
- }
310
- if (metadata.length > 0) {
311
- rows.push({ kind: 'detail', text: ` ${metadata.join(' | ')}`, fg: C.dim, bg: '' });
312
- }
313
- }
314
- }
315
- } else if (this.section === 'models') {
316
- const models = this.providerRegistry?.listModels() ?? [];
317
- const filtered = q ? models.filter(m => m.id.toLowerCase().includes(q) || m.displayName.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q)) : models;
318
- if (filtered.length === 0) {
319
- rows.push({ kind: 'empty', text: q ? ` No models match "${this.searchQuery}". Press Esc to clear the filter.` : ' Provider registry not wired into this session.', fg: C.dim, bg: '' });
320
- } else {
321
- // Group by provider
322
- const byProvider = new Map<string, typeof filtered>();
323
- for (const m of filtered) {
324
- let arr = byProvider.get(m.provider);
325
- if (!arr) { arr = []; byProvider.set(m.provider, arr); }
326
- arr.push(m);
327
- }
328
- // Live active-model marker (WO-136): Enter on a selectable row calls
329
- // providerRegistry.setCurrentModel, so re-derive this on every build
330
- // instead of caching it — it can change without this panel's input.
331
- const activeKey = this.providerRegistry?.getCurrentModel?.()?.registryKey;
332
- for (const [provider, pModels] of byProvider) {
333
- rows.push({ kind: 'header', text: ` ${provider} (${pModels.length})`, fg: C.sectionFg, bg: C.sectionBg, bold: true });
334
- for (const m of pModels) {
335
- const ctxK = m.contextWindow > 0 ? `${(m.contextWindow / 1000).toFixed(0)}k` : '?';
336
- const caps = [m.contextWindow > 0 ? `ctx:${ctxK}` : ''].filter(Boolean).join(' ');
337
- const isActive = m.registryKey === activeKey;
338
- const activeTag = isActive ? ' ACTIVE' : '';
339
- rows.push({
340
- kind: 'item',
341
- text: ` ${m.displayName} ${caps}${activeTag}`,
342
- fg: isActive ? C.good : C.toolFg,
343
- bg: '',
344
- modelKey: m.registryKey,
345
- modelSelectable: m.selectable,
346
- });
347
- rows.push({ kind: 'detail', text: ` ${m.id}${m.selectable ? '' : ' (not selectable)'}`, fg: C.label, bg: '' });
348
- }
349
- }
350
- }
351
- } else {
352
- // Shortcuts — live bindings from KeybindingsManager.getAll() (WO-136),
353
- // including any user overrides from ~/.goodvibes/tui/keybindings.json.
354
- const km = this.keybindingsManager;
355
- const shortcuts = (km?.getAll() ?? []).map((entry) => ({
356
- key: entry.combos.length > 0 ? entry.combos.map((combo) => km!.formatCombo(combo)).join(', ') : '(unbound)',
357
- desc: entry.description,
358
- }));
359
- const filtered = q ? shortcuts.filter(s => s.key.toLowerCase().includes(q) || s.desc.toLowerCase().includes(q)) : shortcuts;
360
- if (shortcuts.length === 0) {
361
- rows.push({ kind: 'empty', text: ' Keybindings manager not wired into this session.', fg: C.dim, bg: '' });
362
- } else {
363
- rows.push({ kind: 'header', text: ' Keyboard Shortcuts', fg: C.sectionFg, bg: C.sectionBg, bold: true });
364
- for (const s of filtered) {
365
- const key = s.key.padEnd(20);
366
- rows.push({ kind: 'item', text: ` ${key} ${s.desc}`, fg: C.value, bg: '' });
367
- }
368
- }
369
- }
370
-
371
- this.rows = rows;
372
- this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, rows.length - 1));
373
- this.markDirty();
374
- }
375
- }