@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.
- package/CHANGELOG.md +85 -21
- package/README.md +20 -11
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/conversation-rendering.ts +19 -0
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +36 -0
- package/src/input/command-registry.ts +38 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +15 -7
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-feed-routes.ts +61 -23
- package/src/input/handler-feed.ts +47 -23
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +6 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +24 -27
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +9 -43
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +151 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +13 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/turn-injection.ts +114 -0
- package/src/runtime/bootstrap-command-context.ts +21 -1
- package/src/runtime/bootstrap-command-parts.ts +34 -7
- package/src/runtime/bootstrap-core.ts +14 -4
- package/src/runtime/bootstrap-shell.ts +29 -16
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
package/src/panels/docs-panel.ts
DELETED
|
@@ -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
|
-
}
|