@pellux/goodvibes-agent 1.4.4 → 1.5.1

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 (230) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +52 -7
  3. package/dist/package/main.js +7491 -12253
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +1 -1
  6. package/docs/getting-started.md +2 -2
  7. package/docs/release-and-publishing.md +1 -1
  8. package/docs/tools-and-commands.md +5 -5
  9. package/package.json +4 -2
  10. package/release/live-verification/live-verification.json +13 -13
  11. package/release/live-verification/live-verification.md +15 -15
  12. package/release/performance-snapshot.json +2 -2
  13. package/release/release-notes.md +5 -7
  14. package/release/release-readiness.json +6 -6
  15. package/src/agent/behavior-discovery-summary.ts +4 -18
  16. package/src/agent/calendar-registry.ts +322 -0
  17. package/src/agent/competitive-feature-inventory.ts +268 -130
  18. package/src/agent/document-registry.ts +5 -1
  19. package/src/agent/email/email-service.ts +350 -0
  20. package/src/agent/email/imap-client.ts +596 -0
  21. package/src/agent/email/smtp-client.ts +453 -0
  22. package/src/agent/ics-calendar.ts +662 -0
  23. package/src/agent/ics-timezone.ts +172 -0
  24. package/src/agent/markdown-frontmatter.ts +31 -0
  25. package/src/agent/memory-safety.ts +1 -1
  26. package/src/agent/note-registry.ts +3 -9
  27. package/src/agent/persona-discovery.ts +3 -15
  28. package/src/agent/persona-registry.ts +1 -10
  29. package/src/agent/research-source-registry.ts +5 -1
  30. package/src/agent/routine-discovery.ts +3 -15
  31. package/src/agent/runtime-profile.ts +3 -0
  32. package/src/agent/skill-discovery.ts +3 -15
  33. package/src/agent/skill-draft-proposer.ts +203 -0
  34. package/src/agent/skill-draft-runner.ts +201 -0
  35. package/src/agent/skill-registry.ts +36 -1
  36. package/src/agent/skill-standard.ts +99 -0
  37. package/src/agent/vibe-file.ts +7 -22
  38. package/src/cli/completion.ts +1 -1
  39. package/src/cli/config-overrides.ts +10 -1
  40. package/src/cli/redaction.ts +17 -5
  41. package/src/config/provider-model.ts +2 -1
  42. package/src/config/secret-config.ts +13 -6
  43. package/src/core/activity-feed.ts +97 -0
  44. package/src/core/away-digest.ts +161 -0
  45. package/src/core/conversation-rendering.ts +7 -3
  46. package/src/core/conversation.ts +22 -15
  47. package/src/core/hardware-profile.ts +362 -0
  48. package/src/core/last-seen-store.ts +78 -0
  49. package/src/core/plain-language.ts +52 -0
  50. package/src/core/setup-incomplete-hint.ts +90 -0
  51. package/src/core/system-message-router.ts +38 -87
  52. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  53. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  54. package/src/input/agent-workspace-categories.ts +40 -125
  55. package/src/input/agent-workspace-command-editor.ts +4 -0
  56. package/src/input/agent-workspace-host-category.ts +0 -5
  57. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  58. package/src/input/agent-workspace-navigation.ts +10 -3
  59. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  60. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  61. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  62. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  63. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  64. package/src/input/agent-workspace-profile-editors.ts +155 -0
  65. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  66. package/src/input/agent-workspace-types.ts +5 -1
  67. package/src/input/agent-workspace.ts +65 -39
  68. package/src/input/command-registry.ts +18 -5
  69. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  70. package/src/input/commands/agent-skills-runtime.ts +60 -3
  71. package/src/input/commands/calendar-runtime.ts +209 -0
  72. package/src/input/commands/channels-runtime.ts +1 -0
  73. package/src/input/commands/command-error.ts +9 -0
  74. package/src/input/commands/compat-runtime.ts +1 -0
  75. package/src/input/commands/config.ts +1 -0
  76. package/src/input/commands/conversation-runtime.ts +1 -1
  77. package/src/input/commands/delegation-runtime.ts +5 -6
  78. package/src/input/commands/email-runtime.ts +387 -0
  79. package/src/input/commands/experience-runtime.ts +17 -4
  80. package/src/input/commands/guidance-runtime.ts +1 -1
  81. package/src/input/commands/health-runtime.ts +6 -1
  82. package/src/input/commands/knowledge-format.ts +73 -0
  83. package/src/input/commands/knowledge.ts +9 -74
  84. package/src/input/commands/local-provider-runtime.ts +2 -1
  85. package/src/input/commands/local-runtime.ts +10 -4
  86. package/src/input/commands/mcp-runtime.ts +7 -0
  87. package/src/input/commands/notify-runtime.ts +17 -1
  88. package/src/input/commands/onboarding-runtime.ts +1 -0
  89. package/src/input/commands/operator-actions-runtime.ts +1 -0
  90. package/src/input/commands/operator-runtime.ts +3 -0
  91. package/src/input/commands/personas-runtime.ts +1 -0
  92. package/src/input/commands/platform-access-runtime.ts +40 -17
  93. package/src/input/commands/product-runtime.ts +6 -1
  94. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  95. package/src/input/commands/qrcode-runtime.ts +1 -0
  96. package/src/input/commands/routines-runtime.ts +1 -0
  97. package/src/input/commands/runtime-services.ts +0 -13
  98. package/src/input/commands/security-runtime.ts +1 -0
  99. package/src/input/commands/session-content.ts +1 -0
  100. package/src/input/commands/shell-core.ts +5 -2
  101. package/src/input/commands/subscription-runtime.ts +33 -9
  102. package/src/input/commands/support-bundle-runtime.ts +8 -1
  103. package/src/input/commands/tasks-runtime.ts +1 -0
  104. package/src/input/commands/tts-runtime.ts +1 -0
  105. package/src/input/commands/vibe-runtime.ts +1 -0
  106. package/src/input/commands.ts +4 -0
  107. package/src/input/feed-context-factory.ts +3 -12
  108. package/src/input/handler-command-route.ts +7 -60
  109. package/src/input/handler-feed-routes.ts +0 -194
  110. package/src/input/handler-feed.ts +2 -54
  111. package/src/input/handler-interactions.ts +0 -2
  112. package/src/input/handler-modal-stack.ts +0 -9
  113. package/src/input/handler-picker-routes.ts +24 -1
  114. package/src/input/handler-shortcuts.ts +11 -40
  115. package/src/input/handler-ui-state.ts +13 -0
  116. package/src/input/handler.ts +8 -35
  117. package/src/input/keybindings.ts +40 -17
  118. package/src/input/model-picker-local-fit.ts +130 -0
  119. package/src/input/shell-passthrough.ts +58 -0
  120. package/src/input/submission-router.ts +0 -5
  121. package/src/main.ts +129 -90
  122. package/src/renderer/activity-sidebar.ts +186 -0
  123. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  124. package/src/renderer/agent-workspace-style.ts +1 -1
  125. package/src/renderer/agent-workspace.ts +14 -2
  126. package/src/renderer/compositor.ts +37 -125
  127. package/src/renderer/conversation-overlays.ts +3 -3
  128. package/src/renderer/help-overlay.ts +7 -5
  129. package/src/renderer/model-workspace.ts +101 -86
  130. package/src/{panels → renderer}/polish.ts +3 -3
  131. package/src/renderer/shell-surface.ts +4 -5
  132. package/src/renderer/status-token.ts +31 -11
  133. package/src/renderer/tab-strip.ts +1 -1
  134. package/src/renderer/tool-call.ts +7 -8
  135. package/src/renderer/tool-labels.ts +92 -0
  136. package/src/renderer/ui-factory.ts +48 -96
  137. package/src/runtime/bootstrap-command-context.ts +0 -5
  138. package/src/runtime/bootstrap-command-parts.ts +6 -15
  139. package/src/runtime/bootstrap-core.ts +34 -13
  140. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  141. package/src/runtime/bootstrap-shell.ts +3 -50
  142. package/src/runtime/bootstrap.ts +3 -4
  143. package/src/runtime/context.ts +1 -1
  144. package/src/runtime/diagnostics/panels/index.ts +0 -1
  145. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  146. package/src/runtime/execution-ledger.ts +16 -1
  147. package/src/runtime/index.ts +6 -1
  148. package/src/runtime/onboarding/index.ts +1 -0
  149. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  150. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  151. package/src/runtime/services.ts +16 -4
  152. package/src/runtime/terminal-output-guard.ts +7 -0
  153. package/src/runtime/tool-permission-safety.ts +1 -1
  154. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  155. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  156. package/src/runtime/ui/model-picker/types.ts +2 -2
  157. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  158. package/src/runtime/ui/provider-health/types.ts +2 -2
  159. package/src/runtime/ui-services.ts +0 -2
  160. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  161. package/src/shell/autonomy-surfacing.ts +272 -0
  162. package/src/shell/session-continuity-hints.ts +0 -6
  163. package/src/shell/startup-wiring.ts +221 -0
  164. package/src/shell/ui-openers.ts +18 -29
  165. package/src/tools/agent-context-policy.ts +1 -1
  166. package/src/tools/agent-harness-background-processes.ts +4 -4
  167. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  168. package/src/tools/agent-harness-command-runner.ts +6 -1
  169. package/src/tools/agent-harness-document-ops.ts +26 -53
  170. package/src/tools/agent-harness-execution-history.ts +1 -13
  171. package/src/tools/agent-harness-execution-posture.ts +0 -15
  172. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  173. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  174. package/src/tools/agent-harness-metadata.ts +16 -0
  175. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  176. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  177. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  178. package/src/tools/agent-harness-tool-schema.ts +13 -14
  179. package/src/tools/agent-harness-tool.ts +27 -23
  180. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  181. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  182. package/src/tools/agent-workspace-tool.ts +2 -33
  183. package/src/utils/terminal-width.ts +9 -3
  184. package/src/version.ts +1 -1
  185. package/src/input/agent-workspace-panel-route.ts +0 -44
  186. package/src/input/panel-integration-actions.ts +0 -26
  187. package/src/panels/approval-panel.ts +0 -149
  188. package/src/panels/automation-control-panel.ts +0 -212
  189. package/src/panels/base-panel.ts +0 -254
  190. package/src/panels/builtin/agent.ts +0 -58
  191. package/src/panels/builtin/knowledge.ts +0 -26
  192. package/src/panels/builtin/operations.ts +0 -121
  193. package/src/panels/builtin/session.ts +0 -138
  194. package/src/panels/builtin/shared.ts +0 -275
  195. package/src/panels/builtin/usage.ts +0 -21
  196. package/src/panels/builtin-panels.ts +0 -23
  197. package/src/panels/confirm-state.ts +0 -61
  198. package/src/panels/context-visualizer-panel.ts +0 -204
  199. package/src/panels/cost-tracker-panel.ts +0 -444
  200. package/src/panels/docs-panel.ts +0 -285
  201. package/src/panels/index.ts +0 -25
  202. package/src/panels/knowledge-panel.ts +0 -417
  203. package/src/panels/memory-panel.ts +0 -226
  204. package/src/panels/panel-list-panel.ts +0 -464
  205. package/src/panels/panel-manager.ts +0 -570
  206. package/src/panels/provider-accounts-panel.ts +0 -233
  207. package/src/panels/provider-health-domains.ts +0 -208
  208. package/src/panels/provider-health-panel.ts +0 -720
  209. package/src/panels/provider-health-tracker.ts +0 -115
  210. package/src/panels/provider-stats-panel.ts +0 -366
  211. package/src/panels/qr-panel.ts +0 -207
  212. package/src/panels/schedule-panel.ts +0 -321
  213. package/src/panels/scrollable-list-panel.ts +0 -491
  214. package/src/panels/search-focus.ts +0 -32
  215. package/src/panels/security-panel.ts +0 -295
  216. package/src/panels/session-browser-panel.ts +0 -395
  217. package/src/panels/session-maintenance.ts +0 -125
  218. package/src/panels/subscription-panel.ts +0 -263
  219. package/src/panels/system-messages-panel.ts +0 -230
  220. package/src/panels/tasks-panel.ts +0 -344
  221. package/src/panels/thinking-panel.ts +0 -304
  222. package/src/panels/token-budget-panel.ts +0 -475
  223. package/src/panels/tool-inspector-panel.ts +0 -436
  224. package/src/panels/types.ts +0 -54
  225. package/src/renderer/panel-composite.ts +0 -158
  226. package/src/renderer/panel-tab-bar.ts +0 -69
  227. package/src/renderer/panel-workspace-bar.ts +0 -42
  228. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  229. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  230. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -1,464 +0,0 @@
1
- /**
2
- * PanelListPanel — Shows registered panel routes grouped by category.
3
- *
4
- * Features:
5
- * - Arrow keys to navigate, Enter to show the matching Agent workspace route
6
- * - Open/closed indicator (● open, ○ closed)
7
- * - Search/filter by typing
8
- * - Grouped by category
9
- *
10
- * Open the current operator workspace via /agent.
11
- */
12
- import type { Line } from '../types/grid.ts';
13
- import type { PanelCategory, PanelRegistration } from './types.ts';
14
- import { BasePanel } from './base-panel.ts';
15
- import type { PanelManager } from './panel-manager.ts';
16
- import type { ComponentHealthMonitor } from '../runtime/perf/panel-health-monitor.ts';
17
- import { createEmptyLine } from '../types/grid.ts';
18
- import {
19
- buildEmptyState,
20
- buildKeyValueLine,
21
- buildPanelListRow,
22
- buildPanelLine,
23
- buildSearchInputLine,
24
- buildSummaryBlock,
25
- buildPanelWorkspace,
26
- DEFAULT_PANEL_PALETTE,
27
- resolvePrimaryScrollableSection,
28
- type PanelWorkspaceSection,
29
- } from './polish.ts';
30
- import { truncateDisplay } from '../utils/terminal-width.ts';
31
- import { wrapWithHangingIndent } from '../renderer/text-layout.ts';
32
- import {
33
- getPanelSearchFocusTransition,
34
- isPanelSearchBackspace,
35
- isPanelSearchCancel,
36
- isPanelSearchCommit,
37
- isPanelSearchPrintable,
38
- } from './search-focus.ts';
39
- import { agentWorkspaceCategoryForPanel, agentWorkspaceCommandForPanel } from '../input/agent-workspace-panel-route.ts';
40
-
41
- // ── Colour palette ────────────────────────────────────────────────────────────
42
- const C = {
43
- ...DEFAULT_PANEL_PALETTE,
44
- header: '#94a3b8',
45
- headerBg: '#1e293b',
46
- category: '#64748b',
47
- categoryBg: '#1e293b',
48
- icon: '#38bdf8',
49
- name: '#e2e8f0',
50
- desc: '#64748b',
51
- openDot: '#22c55e',
52
- closedDot: '#475569',
53
- selected: '#e2e8f0',
54
- selectedBg: '#1e3a5f',
55
- selIcon: '#38bdf8',
56
- hint: '#475569',
57
- search: '#f97316',
58
- searchBg: '#1e293b',
59
- dim: '#334155',
60
- intro: '#94a3b8',
61
- } as const;
62
-
63
- const CATEGORY_ORDER: PanelCategory[] = ['agent', 'monitoring', 'session', 'ai'];
64
- const CATEGORY_LABELS: Record<PanelCategory, string> = {
65
- agent: 'Agent',
66
- monitoring: 'Monitoring',
67
- session: 'Session',
68
- ai: 'AI',
69
- };
70
-
71
- // ── Layout constants ──────────────────────────────────────────────────────────
72
- const NAME_COL_WIDTH = 22;
73
- const PREFIX_WIDTH = 4; // arrow + dot + space + space
74
-
75
- /** A flat entry in the navigable list — either a category header or a panel row. */
76
- type ListEntry =
77
- | { kind: 'header'; category: PanelCategory }
78
- | { kind: 'panel'; reg: PanelRegistration };
79
-
80
- function wrapPanelDescription(text: string, width: number, maxLines = 2): string[] {
81
- if (width <= 0) return [''];
82
- const lines = wrapWithHangingIndent(text, width, '', maxLines);
83
- return lines.length > 0 ? lines.map((line) => truncateDisplay(line, width)) : [''];
84
- }
85
-
86
- // ── PanelListPanel ────────────────────────────────────────────────────────────
87
-
88
- export class PanelListPanel extends BasePanel {
89
- private _selectedIndex = 0;
90
- private _scrollOffset = 0;
91
- private _query = '';
92
- private _filterFocused = false;
93
- private _routeMessage = 'This list is read-only in Agent. Press Enter for the matching Agent workspace route.';
94
- private _cachedEntries: ListEntry[] | null = null;
95
- private _entriesDirty = true;
96
-
97
- public constructor(
98
- private readonly panelManager: PanelManager,
99
- componentHealthMonitor?: ComponentHealthMonitor,
100
- ) {
101
- super('panel-list', 'Panel List', 'L', 'session', componentHealthMonitor);
102
- }
103
-
104
- public override onActivate(): void {
105
- super.onActivate();
106
- this._selectedIndex = 0;
107
- this._scrollOffset = 0;
108
- this._query = '';
109
- this._filterFocused = false;
110
- this._routeMessage = 'This list is read-only in Agent. Press Enter for the matching Agent workspace route.';
111
- this._entriesDirty = true;
112
- }
113
-
114
- protected override markDirty(): void {
115
- super.markDirty();
116
- this._entriesDirty = true;
117
- }
118
-
119
- public handleInput(key: string): boolean {
120
- const entries = this._buildEntries();
121
- const panelCount = entries.filter(e => e.kind === 'panel').length;
122
-
123
- if (this._filterFocused) {
124
- const transition = getPanelSearchFocusTransition(key, { selectedIndex: this._selectedIndex, itemCount: panelCount });
125
- if (transition === 'focus-list') {
126
- this._filterFocused = false;
127
- this._selectedIndex = 0;
128
- this._scrollOffset = 0;
129
- this.markDirty();
130
- return true;
131
- }
132
- if (isPanelSearchBackspace(key)) {
133
- if (this._query.length === 0) return true;
134
- this._query = this._query.slice(0, -1);
135
- this._clampSelection(entries);
136
- this.markDirty();
137
- return true;
138
- }
139
- if (isPanelSearchCancel(key)) {
140
- this._filterFocused = false;
141
- this.markDirty();
142
- return true;
143
- }
144
- if (isPanelSearchCommit(key)) {
145
- this._filterFocused = false;
146
- const selectedPanel = this._getSelectedPanelEntry(this._buildEntries());
147
- if (selectedPanel) {
148
- this._setRouteMessage(selectedPanel.reg);
149
- }
150
- this.markDirty();
151
- return true;
152
- }
153
- if (isPanelSearchPrintable(key)) {
154
- this._query += key;
155
- this._selectedIndex = 0;
156
- this._scrollOffset = 0;
157
- this.markDirty();
158
- return true;
159
- }
160
- return false;
161
- }
162
-
163
- const transition = getPanelSearchFocusTransition(key, { selectedIndex: this._selectedIndex, itemCount: panelCount });
164
- if (transition === 'focus-search') {
165
- this._filterFocused = true;
166
- this.markDirty();
167
- return true;
168
- }
169
-
170
- // Navigation
171
- if (key === 'up' || key === 'k') {
172
- this._movePrevPanel();
173
- this.markDirty();
174
- return true;
175
- }
176
- if (key === 'down' || key === 'j') {
177
- this._moveNextPanel(entries);
178
- this.markDirty();
179
- return true;
180
- }
181
-
182
- // Route selected panel through the Agent workspace.
183
- if (key === 'return' || key === 'enter') {
184
- const selectedPanel = this._getSelectedPanelEntry(entries);
185
- if (selectedPanel) {
186
- this._setRouteMessage(selectedPanel.reg);
187
- this.markDirty();
188
- }
189
- return true;
190
- }
191
-
192
- if (key === 'T' || key === 'B' || key === 'M' || key === 'S' || key === 'tab') {
193
- const selectedPanel = this._getSelectedPanelEntry(entries);
194
- if (selectedPanel) {
195
- const command = agentWorkspaceCommandForPanel(selectedPanel.reg.id);
196
- this._routeMessage = `Panel placement keys are inactive in Agent. Use ${command} for ${selectedPanel.reg.name}.`;
197
- } else {
198
- this._routeMessage = 'Panel placement keys are inactive in Agent. Use /agent for current operator controls.';
199
- }
200
- this.markDirty();
201
- return true;
202
- }
203
-
204
- // Search: backspace
205
- if (isPanelSearchBackspace(key)) {
206
- if (this._query.length > 0) {
207
- this._query = this._query.slice(0, -1);
208
- this._clampSelection(entries);
209
- this.markDirty();
210
- return true;
211
- }
212
- return false;
213
- }
214
-
215
- // Escape: clear search
216
- if (isPanelSearchCancel(key)) {
217
- if (this._query.length > 0) {
218
- this._query = '';
219
- this._clampSelection(entries);
220
- this.markDirty();
221
- return true;
222
- }
223
- return false;
224
- }
225
-
226
- // Printable character: append to search query
227
- return false;
228
- }
229
-
230
- public render(width: number, height: number): Line[] {
231
- if (!this.canRenderNow()) {
232
- return Array.from({ length: height }, () => createEmptyLine(width));
233
- }
234
- const start = Date.now();
235
- this.needsRender = false;
236
- const intro = 'Browse registered panel routes and jump to the matching Agent workspace area.';
237
- const entries = this._buildEntries();
238
-
239
- if (entries.length === 0) {
240
- const lines = buildPanelWorkspace(width, height, {
241
- title: 'Agent Panel Routes',
242
- intro,
243
- sections: [{
244
- title: 'Filter',
245
- lines: [buildSearchInputLine(width, 'Filter: ', `${this._query}${this._filterFocused ? '_' : ''}`, C, {
246
- active: this._filterFocused,
247
- bg: C.searchBg,
248
- emptyLabel: this._filterFocused ? '(type to filter)' : '(/ or up at top)',
249
- valueColor: C.search,
250
- })],
251
- }, {
252
- lines: buildEmptyState(
253
- width,
254
- ' No panels match filter.',
255
- 'Clear the filter or search for another panel by id, name, description, or category.',
256
- [{ command: '/agent', summary: 'open the Agent workspace' }],
257
- C,
258
- ),
259
- }],
260
- palette: C,
261
- });
262
- while (lines.length < height) lines.push(createEmptyLine(width));
263
- if (lines.length > height) lines.length = height;
264
- this.reportRenderDuration(Date.now() - start);
265
- return lines;
266
- }
267
-
268
- const panelEntries = entries.filter(e => e.kind === 'panel');
269
- const pm = this.panelManager;
270
- const topIds = new Set(pm.getTopPane().panels.map(p => p.id));
271
- const bottomIds = new Set(pm.getBottomPane().panels.map(p => p.id));
272
- const selectedPanel = this._getSelectedPanelEntry(entries);
273
- const selectedRoute = selectedPanel ? agentWorkspaceCommandForPanel(selectedPanel.reg.id) : '/agent';
274
- const selectedArea = selectedPanel ? agentWorkspaceCategoryForPanel(selectedPanel.reg.id) : 'home';
275
- const footerLines = [buildPanelLine(width, [[` [${this._selectedIndex + 1}/${panelEntries.length}] ↑/↓ nav Enter route / filter Esc clear`.slice(0, width), C.hint]])];
276
- const postureLines: Line[] = [
277
- buildKeyValueLine(width, [
278
- { label: 'copied open', value: String(pm.getAllOpen().length), valueColor: pm.getAllOpen().length > 0 ? C.name : C.dim },
279
- { label: 'agent route', value: selectedRoute, valueColor: C.info },
280
- { label: 'area', value: selectedArea, valueColor: C.value },
281
- { label: 'results', value: String(panelEntries.length), valueColor: C.value },
282
- ], C),
283
- buildPanelLine(width, [[` ${truncateDisplay(this._routeMessage, Math.max(0, width - 1))}`, C.intro]]),
284
- ];
285
- const entryLines: Line[] = [
286
- buildSearchInputLine(width, 'Filter: ', `${this._query}${this._filterFocused ? '_' : ''}`, C, {
287
- active: this._filterFocused,
288
- bg: C.searchBg,
289
- emptyLabel: this._filterFocused ? '(type to filter)' : '(/ or up at top)',
290
- valueColor: C.search,
291
- }),
292
- ];
293
- const renderedBlocks: Array<{ entry: ListEntry; lines: Line[]; panelFlatIndex?: number }> = [];
294
- let flatPanelIndex = 0;
295
- for (const entry of entries) {
296
- if (entry.kind === 'header') {
297
- const label = ` ── ${CATEGORY_LABELS[entry.category]} ${'─'.repeat(Math.max(0, width - 6 - CATEGORY_LABELS[entry.category].length))}`;
298
- renderedBlocks.push({
299
- entry,
300
- lines: [buildPanelLine(width, [[label.slice(0, width), C.category, C.categoryBg]])],
301
- });
302
- } else {
303
- const flatIdx = flatPanelIndex++;
304
- const isSelected = flatIdx === this._selectedIndex;
305
- const isTopOpen = topIds.has(entry.reg.id);
306
- const isBottomOpen = bottomIds.has(entry.reg.id);
307
- const dot = isTopOpen || isBottomOpen ? '●' : '○';
308
- const dotColor = isTopOpen || isBottomOpen ? C.openDot : C.closedDot;
309
- const nameColor = isSelected ? C.selected : C.name;
310
- const nameStr = entry.reg.name.padEnd(NAME_COL_WIDTH, ' ').slice(0, NAME_COL_WIDTH);
311
- const descStartCol = PREFIX_WIDTH + NAME_COL_WIDTH + 1;
312
- const descWidth = Math.max(1, width - descStartCol);
313
- const descLines = wrapPanelDescription(entry.reg.description, descWidth, 2);
314
- const routeCategory = agentWorkspaceCategoryForPanel(entry.reg.id);
315
- const blockLines: Line[] = [
316
- buildPanelListRow(width, [
317
- { text: dot, fg: dotColor },
318
- { text: routeCategory.charAt(0).toUpperCase(), fg: isSelected ? C.selIcon : C.icon },
319
- { text: ' ', fg: C.dim },
320
- { text: `${nameStr} `, fg: nameColor },
321
- { text: descLines[0] ?? '', fg: C.desc },
322
- ], C, { selected: isSelected, selectedBg: C.selectedBg, markerColor: C.selIcon }),
323
- ];
324
- if ((descLines[1] ?? '').length > 0) {
325
- blockLines.push(buildPanelLine(width, [
326
- [' '.repeat(PREFIX_WIDTH), C.dim, isSelected ? C.selectedBg : C.surfaceBg],
327
- [' '.repeat(NAME_COL_WIDTH), C.dim, isSelected ? C.selectedBg : C.surfaceBg],
328
- [' ', C.dim, isSelected ? C.selectedBg : C.surfaceBg],
329
- [descLines[1] ?? '', C.desc, isSelected ? C.selectedBg : C.surfaceBg],
330
- ]));
331
- }
332
- renderedBlocks.push({ entry, lines: blockLines, panelFlatIndex: flatIdx });
333
- }
334
- }
335
- const blockStarts: number[] = [];
336
- let totalRows = 0;
337
- for (const block of renderedBlocks) {
338
- blockStarts.push(totalRows);
339
- totalRows += block.lines.length;
340
- }
341
- const selectedEntryIndex = renderedBlocks.findIndex((block) => block.panelFlatIndex === this._selectedIndex);
342
- const selectedRow = selectedEntryIndex >= 0 ? blockStarts[selectedEntryIndex] ?? 0 : 0;
343
- const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Route posture', postureLines, C) };
344
- const resolvedSection = resolvePrimaryScrollableSection(width, height, {
345
- intro,
346
- footerLines,
347
- palette: C,
348
- beforeSections: [postureSection],
349
- section: {
350
- title: 'Panels',
351
- fixedLines: entryLines,
352
- scrollableLines: renderedBlocks.flatMap((block) => block.lines),
353
- selectedIndex: selectedRow,
354
- scrollOffset: this._scrollOffset,
355
- guardRows: 1,
356
- minRows: 1,
357
- appendWindowSummary: {
358
- dimColor: C.dim,
359
- },
360
- },
361
- });
362
- this._scrollOffset = resolvedSection.scrollOffset;
363
-
364
- const sections: PanelWorkspaceSection[] = [
365
- postureSection,
366
- resolvedSection.section,
367
- ];
368
- const lines = buildPanelWorkspace(width, height, {
369
- title: 'Agent Panel Routes',
370
- intro,
371
- sections,
372
- footerLines,
373
- palette: C,
374
- });
375
-
376
- while (lines.length < height) lines.push(createEmptyLine(width));
377
- if (lines.length > height) lines.length = height;
378
- this.reportRenderDuration(Date.now() - start);
379
- return lines;
380
- }
381
-
382
- // ── Private helpers ───────────────────────────────────────────────────────
383
-
384
- /** Build flat list of entries (headers + panel rows) filtered by query. */
385
- private _buildEntries(): ListEntry[] {
386
- if (this._cachedEntries !== null && !this._entriesDirty) {
387
- return this._cachedEntries;
388
- }
389
-
390
- const manager = this.panelManager;
391
- const byCategory = manager.getTypesByCategory();
392
- const q = this._query.toLowerCase();
393
- const entries: ListEntry[] = [];
394
-
395
- for (const cat of CATEGORY_ORDER) {
396
- const regs = byCategory.get(cat) ?? [];
397
- const filtered = q
398
- ? regs.filter(r =>
399
- r.id.toLowerCase().includes(q) ||
400
- r.name.toLowerCase().includes(q) ||
401
- r.description.toLowerCase().includes(q) ||
402
- r.category.toLowerCase().includes(q)
403
- )
404
- : regs;
405
-
406
- if (filtered.length === 0) continue;
407
-
408
- entries.push({ kind: 'header', category: cat });
409
- for (const reg of filtered) {
410
- entries.push({ kind: 'panel', reg });
411
- }
412
- }
413
-
414
- this._cachedEntries = entries;
415
- this._entriesDirty = false;
416
- return entries;
417
- }
418
-
419
- /** Get the flat panel index (counting only panel entries) for a given panel id. */
420
- private _flatPanelIndex(entries: ListEntry[], id: string): number {
421
- let idx = 0;
422
- for (const e of entries) {
423
- if (e.kind !== 'panel') continue;
424
- if (e.reg.id === id) return idx;
425
- idx++;
426
- }
427
- return -1;
428
- }
429
-
430
- /** Get the ListEntry for the currently selected panel. */
431
- private _getSelectedPanelEntry(entries: ListEntry[]): Extract<ListEntry, { kind: 'panel' }> | null {
432
- let idx = 0;
433
- for (const e of entries) {
434
- if (e.kind !== 'panel') continue;
435
- if (idx === this._selectedIndex) return e;
436
- idx++;
437
- }
438
- return null;
439
- }
440
-
441
- private _setRouteMessage(reg: PanelRegistration): void {
442
- const command = agentWorkspaceCommandForPanel(reg.id);
443
- const area = agentWorkspaceCategoryForPanel(reg.id);
444
- this._routeMessage = `${reg.name} routes through ${command} (${area}) in Agent workspace.`;
445
- }
446
-
447
- /** Move selection to the previous panel entry. */
448
- private _movePrevPanel(): void {
449
- this._selectedIndex = Math.max(0, this._selectedIndex - 1);
450
- }
451
-
452
- /** Move selection to the next panel entry. */
453
- private _moveNextPanel(entries: ListEntry[]): void {
454
- const panelCount = entries.filter(e => e.kind === 'panel').length;
455
- this._selectedIndex = Math.min(panelCount - 1, this._selectedIndex + 1);
456
- }
457
-
458
- /** Clamp selectedIndex within available panels. */
459
- private _clampSelection(entries: ListEntry[]): void {
460
- const panelCount = entries.filter(e => e.kind === 'panel').length;
461
- this._selectedIndex = Math.max(0, Math.min(this._selectedIndex, panelCount - 1));
462
- }
463
-
464
- }