@pellux/goodvibes-tui 1.1.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,384 +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
- /**
126
- * The `/`-to-search buffer wants every character of a burst (paste, or a
127
- * fast-typed query landing in one input.feed() call), same as it always
128
- * has — see the interface doc on `Panel.isCapturingTextBurst`.
129
- */
130
- isCapturingTextBurst(): boolean {
131
- return this.searching;
132
- }
133
-
134
- handleInput(key: string): boolean {
135
- const searchResult = this._handleSearchKey(key);
136
- if (searchResult !== null) return searchResult;
137
-
138
- switch (key) {
139
- case 'up': this._move(-1); return true;
140
- case 'down': this._move(1); return true;
141
- case 'pageup': this._move(-10); return true;
142
- case 'pagedown': this._move(10); return true;
143
- case 't': this._setSection('tools'); return true;
144
- case 'm': this._setSection('models'); return true;
145
- case 'k': this._setSection('shortcuts'); return true;
146
- case 'enter':
147
- case 'return': return this._activateSelected();
148
- default: return false;
149
- }
150
- }
151
-
152
- /**
153
- * Enter on the cursor row. Tool rows can't act directly — opening the tool
154
- * inspector needs the PanelManager, which only handlePanelIntegrationAction
155
- * has — so this just records intent and lets that hook finish the jump.
156
- * Model rows need no cross-panel access: switching the active model is a
157
- * direct providerRegistry call, so it happens right here.
158
- */
159
- private _activateSelected(): boolean {
160
- const row = this.rows[this.cursorIndex];
161
- if (!row || row.kind !== 'item') return false;
162
- if (this.section === 'tools' && row.toolName) {
163
- this._pendingToolJump = row.toolName;
164
- return true;
165
- }
166
- if (this.section === 'models' && row.modelKey) {
167
- if (row.modelSelectable && this.providerRegistry?.setCurrentModel) {
168
- this.providerRegistry.setCurrentModel(row.modelKey);
169
- this._buildRows();
170
- }
171
- return true;
172
- }
173
- return false;
174
- }
175
-
176
- /**
177
- * Cross-panel integration hook — opens ToolInspectorPanel filtered to the
178
- * tool selected on Enter (session.ts:102 sibling-open pattern), consuming
179
- * the intent recorded by _activateSelected.
180
- */
181
- handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
182
- if (!this._pendingToolJump) return false;
183
- const tool = this._pendingToolJump;
184
- this._pendingToolJump = null;
185
- const inspector = ctx.panelManager.open('tools');
186
- if (!(inspector instanceof ToolInspectorPanel)) return false;
187
- inspector.filterByTool(tool);
188
- return true;
189
- }
190
-
191
- render(width: number, height: number): Line[] {
192
- if (height <= 0 || width <= 0) return [];
193
- const sectionLabel = this.section === 'tools' ? 'Tools' : this.section === 'models' ? 'Models' : 'Shortcuts';
194
- this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, Math.max(0, this.rows.length - 1)));
195
- const controlsSection = {
196
- title: 'Controls',
197
- lines: [
198
- buildPanelLine(width, [
199
- [' t', DEFAULT_PANEL_PALETTE.info], [' tools', DEFAULT_PANEL_PALETTE.dim],
200
- [' m', DEFAULT_PANEL_PALETTE.info], [' models', DEFAULT_PANEL_PALETTE.dim],
201
- [' k', DEFAULT_PANEL_PALETTE.info], [' shortcuts', DEFAULT_PANEL_PALETTE.dim],
202
- [' /', DEFAULT_PANEL_PALETTE.info], [' filter', DEFAULT_PANEL_PALETTE.dim],
203
- ]),
204
- this._buildFilterLine(width),
205
- ],
206
- } as const;
207
- // Context-aware footer: while filtering, surface only the keys that work in
208
- // the filter field; otherwise surface section + navigation keys.
209
- const footerLines = [this.searching
210
- ? buildKeyboardHints(width, [
211
- { keys: 'type', label: 'filter' },
212
- { keys: 'Enter', label: 'apply' },
213
- { keys: 'Esc', label: 'clear' },
214
- ], DEFAULT_PANEL_PALETTE)
215
- : buildKeyboardHints(width, [
216
- { keys: 't/m/k', label: 'tools / models / shortcuts' },
217
- { keys: '↑/↓', label: 'navigate' },
218
- ...(this.section === 'tools' ? [{ keys: 'Enter', label: 'open in tool inspector' }] : []),
219
- ...(this.section === 'models' ? [{ keys: 'Enter', label: 'set active model' }] : []),
220
- { keys: '/', label: 'filter' },
221
- ], DEFAULT_PANEL_PALETTE)];
222
-
223
- const sectionWindow = resolveScrollablePanelSection(width, height, {
224
- intro: 'Browse built-in tool docs, available models, and keyboard shortcuts from one shared reference surface.',
225
- palette: DEFAULT_PANEL_PALETTE,
226
- footerLines,
227
- beforeSections: [controlsSection],
228
- section: {
229
- title: sectionLabel,
230
- scrollableLines: this.rows.map((row, absIdx) => {
231
- const isCursor = absIdx === this.cursorIndex && row.kind !== 'header' && row.kind !== 'empty';
232
- return renderRow(width, row, isCursor);
233
- }),
234
- selectedIndex: this.cursorIndex,
235
- scrollOffset: this.scrollOffset,
236
- minRows: 8,
237
- },
238
- });
239
- this.scrollOffset = sectionWindow.scrollOffset;
240
-
241
- return buildPanelWorkspace(width, height, {
242
- title: ` Docs / ${sectionLabel}`,
243
- intro: 'Browse built-in tool docs, available models, and keyboard shortcuts from one shared reference surface.',
244
- sections: [
245
- controlsSection,
246
- sectionWindow.section.lines.length > 0 ? sectionWindow.section : { title: sectionLabel, lines: [buildPanelLine(width, [[' No matching docs', DEFAULT_PANEL_PALETTE.dim]])] },
247
- ],
248
- footerLines,
249
- palette: DEFAULT_PANEL_PALETTE,
250
- });
251
- }
252
-
253
- private _setSection(section: DocSection): void {
254
- this.section = section;
255
- this.searchQuery = '';
256
- this.cursorIndex = 0;
257
- this.scrollOffset = 0;
258
- this._buildRows();
259
- }
260
-
261
- private _startSearch(): void {
262
- this.searching = true;
263
- this.markDirty();
264
- }
265
-
266
- /**
267
- * The filter input line — pinned rendering contract shared with
268
- * ScrollableListPanel.buildFilterLine: 'Filter: ' unfocused / '[Filter] '
269
- * focused, literal trailing '_' cursor while active (active:false is
270
- * passed to buildSearchInputLine to suppress its block-glyph cursor
271
- * substitution).
272
- */
273
- private _buildFilterLine(width: number): Line {
274
- const label = this.searching ? '[Filter] ' : 'Filter: ';
275
- const value = this.searching ? `${this.searchQuery}_` : this.searchQuery;
276
- return buildSearchInputLine(width, label, value, DEFAULT_PANEL_PALETTE, {
277
- active: false,
278
- bg: this.searching ? DEFAULT_PANEL_PALETTE.inputBg : DEFAULT_PANEL_PALETTE.sectionBg,
279
- emptyLabel: '(/ to filter)',
280
- valueColor: this.searching ? DEFAULT_PANEL_PALETTE.info : (this.searchQuery ? DEFAULT_PANEL_PALETTE.value : DEFAULT_PANEL_PALETTE.dim),
281
- });
282
- }
283
-
284
- private _move(delta: number): void {
285
- if (this.rows.length === 0) return;
286
- this.cursorIndex = Math.max(0, Math.min(this.rows.length - 1, this.cursorIndex + delta));
287
- this.markDirty();
288
- }
289
-
290
- private _buildRows(): void {
291
- const q = this.searchQuery.trim().toLowerCase();
292
- const rows: FlatRow[] = [];
293
-
294
- if (this.section === 'tools') {
295
- const tools = this.toolRegistry?.list() ?? [];
296
- const filtered = q ? tools.filter(t => t.definition.name.toLowerCase().includes(q) || (t.definition.description ?? '').toLowerCase().includes(q)) : tools;
297
- if (filtered.length === 0) {
298
- 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: '' });
299
- } else {
300
- rows.push({ kind: 'header', text: ` Tools (${filtered.length})`, fg: C.sectionFg, bg: C.sectionBg, bold: true });
301
- for (const tool of filtered) {
302
- rows.push({ kind: 'item', text: ` ${tool.definition.name}`, fg: C.toolFg, bg: '', bold: true, toolName: tool.definition.name });
303
- if (tool.definition.description) {
304
- rows.push({ kind: 'detail', text: ` ${tool.definition.description}`, fg: C.label, bg: '' });
305
- }
306
- const metadata: string[] = [];
307
- if (tool.definition.sideEffects && tool.definition.sideEffects.length > 0) {
308
- metadata.push(`effects: ${tool.definition.sideEffects.join(', ')}`);
309
- }
310
- if (tool.definition.concurrency) {
311
- metadata.push(`concurrency: ${tool.definition.concurrency}`);
312
- }
313
- if (tool.definition.supportsProgress) {
314
- metadata.push('progress');
315
- }
316
- if (tool.definition.supportsStreamingOutput) {
317
- metadata.push('streaming');
318
- }
319
- if (metadata.length > 0) {
320
- rows.push({ kind: 'detail', text: ` ${metadata.join(' | ')}`, fg: C.dim, bg: '' });
321
- }
322
- }
323
- }
324
- } else if (this.section === 'models') {
325
- const models = this.providerRegistry?.listModels() ?? [];
326
- const filtered = q ? models.filter(m => m.id.toLowerCase().includes(q) || m.displayName.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q)) : models;
327
- if (filtered.length === 0) {
328
- 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: '' });
329
- } else {
330
- // Group by provider
331
- const byProvider = new Map<string, typeof filtered>();
332
- for (const m of filtered) {
333
- let arr = byProvider.get(m.provider);
334
- if (!arr) { arr = []; byProvider.set(m.provider, arr); }
335
- arr.push(m);
336
- }
337
- // Live active-model marker (WO-136): Enter on a selectable row calls
338
- // providerRegistry.setCurrentModel, so re-derive this on every build
339
- // instead of caching it — it can change without this panel's input.
340
- const activeKey = this.providerRegistry?.getCurrentModel?.()?.registryKey;
341
- for (const [provider, pModels] of byProvider) {
342
- rows.push({ kind: 'header', text: ` ${provider} (${pModels.length})`, fg: C.sectionFg, bg: C.sectionBg, bold: true });
343
- for (const m of pModels) {
344
- const ctxK = m.contextWindow > 0 ? `${(m.contextWindow / 1000).toFixed(0)}k` : '?';
345
- const caps = [m.contextWindow > 0 ? `ctx:${ctxK}` : ''].filter(Boolean).join(' ');
346
- const isActive = m.registryKey === activeKey;
347
- const activeTag = isActive ? ' ACTIVE' : '';
348
- rows.push({
349
- kind: 'item',
350
- text: ` ${m.displayName} ${caps}${activeTag}`,
351
- fg: isActive ? C.good : C.toolFg,
352
- bg: '',
353
- modelKey: m.registryKey,
354
- modelSelectable: m.selectable,
355
- });
356
- rows.push({ kind: 'detail', text: ` ${m.id}${m.selectable ? '' : ' (not selectable)'}`, fg: C.label, bg: '' });
357
- }
358
- }
359
- }
360
- } else {
361
- // Shortcuts — live bindings from KeybindingsManager.getAll() (WO-136),
362
- // including any user overrides from ~/.goodvibes/tui/keybindings.json.
363
- const km = this.keybindingsManager;
364
- const shortcuts = (km?.getAll() ?? []).map((entry) => ({
365
- key: entry.combos.length > 0 ? entry.combos.map((combo) => km!.formatCombo(combo)).join(', ') : '(unbound)',
366
- desc: entry.description,
367
- }));
368
- const filtered = q ? shortcuts.filter(s => s.key.toLowerCase().includes(q) || s.desc.toLowerCase().includes(q)) : shortcuts;
369
- if (shortcuts.length === 0) {
370
- rows.push({ kind: 'empty', text: ' Keybindings manager not wired into this session.', fg: C.dim, bg: '' });
371
- } else {
372
- rows.push({ kind: 'header', text: ' Keyboard Shortcuts', fg: C.sectionFg, bg: C.sectionBg, bold: true });
373
- for (const s of filtered) {
374
- const key = s.key.padEnd(20);
375
- rows.push({ kind: 'item', text: ` ${key} ${s.desc}`, fg: C.value, bg: '' });
376
- }
377
- }
378
- }
379
-
380
- this.rows = rows;
381
- this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, rows.length - 1));
382
- this.markDirty();
383
- }
384
- }