@pellux/goodvibes-tui 0.28.0 → 1.0.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 (144) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/src/core/conversation-line-cache.ts +432 -0
  5. package/src/core/conversation-rendering.ts +11 -3
  6. package/src/core/conversation.ts +50 -4
  7. package/src/input/commands/cost-runtime.ts +49 -0
  8. package/src/input/commands/operator-runtime.ts +5 -1
  9. package/src/input/commands.ts +2 -0
  10. package/src/input/handler-content-actions.ts +8 -3
  11. package/src/input/handler-feed-routes.ts +12 -35
  12. package/src/input/handler-feed.ts +4 -8
  13. package/src/input/handler-shortcuts.ts +51 -0
  14. package/src/input/handler.ts +43 -6
  15. package/src/input/input-history.ts +17 -4
  16. package/src/input/keybindings.ts +48 -8
  17. package/src/input/panel-integration-actions.ts +107 -1
  18. package/src/main.ts +19 -11
  19. package/src/panels/agent-inspector-panel.ts +125 -36
  20. package/src/panels/agent-inspector-shared.ts +144 -0
  21. package/src/panels/approval-panel.ts +67 -16
  22. package/src/panels/automation-control-panel.ts +368 -124
  23. package/src/panels/base-panel.ts +1 -1
  24. package/src/panels/builtin/agent.ts +28 -37
  25. package/src/panels/builtin/development.ts +40 -32
  26. package/src/panels/builtin/knowledge.ts +15 -6
  27. package/src/panels/builtin/operations.ts +178 -117
  28. package/src/panels/builtin/session.ts +35 -9
  29. package/src/panels/builtin/shared.ts +98 -6
  30. package/src/panels/cockpit-panel.ts +232 -73
  31. package/src/panels/communication-panel.ts +58 -20
  32. package/src/panels/confirm-state.ts +8 -1
  33. package/src/panels/control-plane-panel.ts +351 -118
  34. package/src/panels/cost-tracker-panel.ts +165 -7
  35. package/src/panels/debug-panel.ts +312 -159
  36. package/src/panels/diff-panel.ts +209 -57
  37. package/src/panels/docs-panel.ts +152 -66
  38. package/src/panels/eval-panel.ts +220 -42
  39. package/src/panels/file-explorer-panel.ts +202 -122
  40. package/src/panels/file-preview-panel.ts +132 -57
  41. package/src/panels/git-panel.ts +310 -129
  42. package/src/panels/hooks-panel.ts +150 -22
  43. package/src/panels/incident-review-panel.ts +223 -35
  44. package/src/panels/index.ts +0 -4
  45. package/src/panels/intelligence-panel.ts +212 -86
  46. package/src/panels/knowledge-graph-panel.ts +461 -101
  47. package/src/panels/local-auth-panel.ts +240 -28
  48. package/src/panels/marketplace-panel.ts +193 -26
  49. package/src/panels/memory-panel.ts +78 -77
  50. package/src/panels/ops-control-panel.ts +146 -29
  51. package/src/panels/ops-strategy-panel.ts +72 -4
  52. package/src/panels/orchestration-panel.ts +231 -69
  53. package/src/panels/panel-list-panel.ts +154 -131
  54. package/src/panels/panel-manager.ts +97 -9
  55. package/src/panels/plan-dashboard-panel.ts +333 -17
  56. package/src/panels/plugins-panel.ts +184 -29
  57. package/src/panels/policy-panel.ts +210 -38
  58. package/src/panels/polish-core.ts +7 -2
  59. package/src/panels/polish.ts +23 -4
  60. package/src/panels/project-planning-answer-actions.ts +134 -0
  61. package/src/panels/project-planning-panel.ts +62 -113
  62. package/src/panels/provider-health-panel.ts +434 -518
  63. package/src/panels/provider-health-routes.ts +203 -0
  64. package/src/panels/provider-health-tracker.ts +194 -6
  65. package/src/panels/provider-health-views.ts +560 -0
  66. package/src/panels/qr-panel.ts +116 -30
  67. package/src/panels/remote-panel.ts +114 -36
  68. package/src/panels/routes-panel.ts +63 -22
  69. package/src/panels/sandbox-panel.ts +174 -42
  70. package/src/panels/scrollable-list-panel.ts +19 -135
  71. package/src/panels/security-panel.ts +133 -33
  72. package/src/panels/services-panel.ts +116 -64
  73. package/src/panels/session-browser-panel.ts +73 -2
  74. package/src/panels/session-maintenance.ts +4 -122
  75. package/src/panels/settings-sync-panel.ts +335 -72
  76. package/src/panels/skills-panel.ts +157 -89
  77. package/src/panels/subscription-panel.ts +86 -33
  78. package/src/panels/symbol-outline-panel.ts +248 -108
  79. package/src/panels/system-messages-panel.ts +114 -13
  80. package/src/panels/tasks-panel.ts +326 -139
  81. package/src/panels/thinking-panel.ts +43 -10
  82. package/src/panels/token-budget-panel.ts +194 -18
  83. package/src/panels/tool-inspector-panel.ts +144 -34
  84. package/src/panels/types.ts +40 -4
  85. package/src/panels/work-plan-panel.ts +280 -17
  86. package/src/panels/worktree-panel.ts +175 -42
  87. package/src/panels/wrfc-panel.ts +116 -24
  88. package/src/renderer/agent-detail-modal.ts +37 -8
  89. package/src/renderer/code-block.ts +58 -28
  90. package/src/renderer/context-inspector.ts +3 -6
  91. package/src/renderer/conversation-overlays.ts +25 -11
  92. package/src/renderer/conversation-surface.ts +1 -21
  93. package/src/renderer/diff-view.ts +6 -4
  94. package/src/renderer/footer-tips.ts +41 -0
  95. package/src/renderer/fullscreen-primitives.ts +23 -17
  96. package/src/renderer/fullscreen-workspace.ts +2 -7
  97. package/src/renderer/help-overlay.ts +91 -14
  98. package/src/renderer/hint-grammar.ts +52 -0
  99. package/src/renderer/history-search-overlay.ts +10 -16
  100. package/src/renderer/layout.ts +7 -7
  101. package/src/renderer/markdown.ts +9 -1
  102. package/src/renderer/modal-factory.ts +23 -15
  103. package/src/renderer/model-picker-overlay.ts +8 -485
  104. package/src/renderer/model-workspace.ts +9 -24
  105. package/src/renderer/overlay-box.ts +23 -19
  106. package/src/renderer/process-indicator.ts +15 -25
  107. package/src/renderer/profile-picker-modal.ts +13 -14
  108. package/src/renderer/prompt-content-width.ts +16 -0
  109. package/src/renderer/search-overlay.ts +27 -6
  110. package/src/renderer/selection-modal-overlay.ts +3 -1
  111. package/src/renderer/semantic-diff.ts +1 -1
  112. package/src/renderer/session-picker-modal.ts +6 -4
  113. package/src/renderer/settings-modal-helpers.ts +10 -34
  114. package/src/renderer/settings-modal.ts +70 -10
  115. package/src/renderer/shell-surface.ts +54 -15
  116. package/src/renderer/status-glyphs.ts +11 -9
  117. package/src/renderer/surface-layout.ts +0 -12
  118. package/src/renderer/term-caps.ts +1 -1
  119. package/src/renderer/theme.ts +60 -3
  120. package/src/renderer/tool-call.ts +6 -20
  121. package/src/renderer/ui-factory.ts +48 -37
  122. package/src/renderer/ui-primitives.ts +41 -3
  123. package/src/runtime/bootstrap-shell.ts +35 -18
  124. package/src/runtime/diagnostics/panels/index.ts +0 -3
  125. package/src/runtime/render-scheduler.ts +80 -0
  126. package/src/shell/ui-openers.ts +14 -22
  127. package/src/utils/format-duration.ts +2 -2
  128. package/src/utils/splash-lines.ts +54 -5
  129. package/src/version.ts +1 -1
  130. package/src/work-plans/work-plan-store.ts +13 -0
  131. package/src/panels/agent-logs-panel.ts +0 -635
  132. package/src/panels/agent-logs-shared.ts +0 -129
  133. package/src/panels/context-visualizer-panel.ts +0 -238
  134. package/src/panels/forensics-panel.ts +0 -378
  135. package/src/panels/provider-account-snapshot.ts +0 -259
  136. package/src/panels/provider-accounts-panel.ts +0 -255
  137. package/src/panels/provider-stats-panel.ts +0 -391
  138. package/src/panels/schedule-panel.ts +0 -365
  139. package/src/panels/watchers-panel.ts +0 -213
  140. package/src/renderer/file-tree.ts +0 -153
  141. package/src/renderer/progress.ts +0 -99
  142. package/src/renderer/status-token.ts +0 -67
  143. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  144. package/src/runtime/diagnostics/panels/policy.ts +0 -177
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type { Line } from '../types/grid.ts';
6
6
  import { BasePanel } from './base-panel.ts';
7
+ import type { PanelIntegrationContext } from './types.ts';
7
8
  import type { SessionInfo } from '@pellux/goodvibes-sdk/platform/sessions';
8
9
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
9
10
  import type { SessionBrowserQuery } from '../runtime/ui-service-queries.ts';
@@ -40,6 +41,15 @@ const C = extendPalette(DEFAULT_PANEL_PALETTE, {
40
41
  countFg: '#88bbcc',
41
42
  });
42
43
 
44
+ // Splits a computed '/command arg1 arg2' string into the { name, args } shape
45
+ // ctx.executeCommand expects — same leading-slash-stripping approach used by
46
+ // remote-panel.ts for its own dispatched command.
47
+ function parseCommand(command: string): { name: string; args: string[] } | null {
48
+ const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
49
+ const [name, ...args] = parts;
50
+ return name ? { name, args } : null;
51
+ }
52
+
43
53
  function shortDate(ts: number): string {
44
54
  const d = new Date(ts);
45
55
  const Y = d.getFullYear();
@@ -87,6 +97,12 @@ export class SessionBrowserPanel extends BasePanel {
87
97
  private loadError = '';
88
98
  private hasLoaded = false;
89
99
  private refreshTimerId: ReturnType<typeof setInterval> | null = null;
100
+ // x = dispatch the computed next-step command ('/remote recover <runner>'
101
+ // or '/session resume <name>') via the same handleInput -> handlePanelIntegrationAction
102
+ // bridge remote-panel.ts uses — handleInput has no ctx.executeCommand.
103
+ private pendingCommand: { name: string; args: string[] } | null = null;
104
+ // On resume, restore any panels the session had open when it was saved.
105
+ private pendingOpenPanels: string[] | null = null;
90
106
 
91
107
  constructor(
92
108
  private readonly sessionManager: SessionBrowserQuery,
@@ -101,6 +117,21 @@ export class SessionBrowserPanel extends BasePanel {
101
117
  this.refreshTimerId = this.registerTimer(setInterval(() => { this._load(); }, 5000));
102
118
  }
103
119
 
120
+ /**
121
+ * Clears any active search/filter and moves the cursor to the session
122
+ * matching `sessionId` (SessionInfo.name) so a sibling panel (e.g.
123
+ * RoutesPanel's Enter-on-binding jump) can land directly on it. No-ops
124
+ * (beyond clearing the filter) if the session isn't found.
125
+ */
126
+ focusSession(sessionId: string): void {
127
+ this.searching = false;
128
+ this.searchQuery = '';
129
+ this._filter();
130
+ const index = this.filtered.findIndex((session) => session.name === sessionId);
131
+ if (index >= 0) this.cursorIndex = index;
132
+ this.markDirty();
133
+ }
134
+
104
135
  override onDeactivate(): void {
105
136
  if (this.refreshTimerId !== null) { this.clearTimer(this.refreshTimerId); this.refreshTimerId = null; }
106
137
  this.searching = false;
@@ -163,10 +194,34 @@ export class SessionBrowserPanel extends BasePanel {
163
194
  case 'return': this._resume(); return true;
164
195
  case 'd': this._promptDelete(); return true;
165
196
  case 'r': this._load(); return true;
197
+ case 'x': this._dispatchNextStep(); return true;
166
198
  default: return false;
167
199
  }
168
200
  }
169
201
 
202
+ /**
203
+ * Drains `pendingCommand` (set by the 'x' key) and `pendingOpenPanels`
204
+ * (set by `_resume()`) via the ctx.executeCommand / PanelManager bridge.
205
+ * Called by the input router immediately after handleInput() consumes the
206
+ * same key (panel-integration-actions.ts's onPanelInputConsumed).
207
+ */
208
+ handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
209
+ let consumed = false;
210
+ if (this.pendingCommand) {
211
+ const { name, args } = this.pendingCommand;
212
+ this.pendingCommand = null;
213
+ void ctx.executeCommand?.(name, args);
214
+ consumed = true;
215
+ }
216
+ if (this.pendingOpenPanels) {
217
+ const openPanels = this.pendingOpenPanels;
218
+ this.pendingOpenPanels = null;
219
+ for (const panelId of openPanels) ctx.panelManager.open(panelId);
220
+ consumed = true;
221
+ }
222
+ return consumed;
223
+ }
224
+
170
225
  render(width: number, height: number): Line[] {
171
226
  if (height <= 0 || width <= 0) return [];
172
227
  const intro = 'Browse, search, resume, and prune saved conversations.';
@@ -196,7 +251,7 @@ export class SessionBrowserPanel extends BasePanel {
196
251
  hints.push({ keys: 'type', label: 'filter' }, { keys: 'Esc/Enter', label: 'apply' });
197
252
  } else {
198
253
  hints.push({ keys: '/', label: this.searchQuery ? 'edit search' : 'search' });
199
- if (hasSelection) hints.push({ keys: 'Enter', label: 'resume' }, { keys: 'd', label: 'delete' });
254
+ if (hasSelection) hints.push({ keys: 'Enter', label: 'resume' }, { keys: 'x', label: 'run next step' }, { keys: 'd', label: 'delete' });
200
255
  hints.push({ keys: 'r', label: 'refresh' });
201
256
  }
202
257
  const footerLines = [
@@ -285,7 +340,7 @@ export class SessionBrowserPanel extends BasePanel {
285
340
  ...formatReturnContextLines(selected.returnContext).map((line) =>
286
341
  buildPanelLine(width, [[' ', DEFAULT_PANEL_PALETTE.dim], [truncateDisplay(line, Math.max(0, width - 2)), DEFAULT_PANEL_PALETTE.dim]])
287
342
  ),
288
- buildPanelLine(width, [[' Next ', DEFAULT_PANEL_PALETTE.label], [selected.returnContext?.remoteRunners?.length ? `/remote recover ${selected.returnContext.remoteRunners[0]}` : '/session resume', DEFAULT_PANEL_PALETTE.dim]]),
343
+ buildPanelLine(width, [[' Next ', DEFAULT_PANEL_PALETTE.label], [this._computeNextStepCommand(selected), DEFAULT_PANEL_PALETTE.dim], [' (x to run)', DEFAULT_PANEL_PALETTE.dim]]),
289
344
  ],
290
345
  }
291
346
  : { title: 'Selected', lines: [] };
@@ -389,9 +444,25 @@ export class SessionBrowserPanel extends BasePanel {
389
444
  private _resume(): void {
390
445
  const sess = this.filtered[this.cursorIndex];
391
446
  if (!sess) return;
447
+ if (sess.returnContext?.openPanels?.length) {
448
+ this.pendingOpenPanels = [...sess.returnContext.openPanels];
449
+ }
392
450
  this.resumeSession?.(sess.name);
393
451
  }
394
452
 
453
+ /** The next-step command shown in the 'Selected' section and dispatched by 'x'. */
454
+ private _computeNextStepCommand(sess: SessionInfo): string {
455
+ const runner = sess.returnContext?.remoteRunners?.[0];
456
+ return runner ? `/remote recover ${runner}` : `/session resume ${sess.name}`;
457
+ }
458
+
459
+ private _dispatchNextStep(): void {
460
+ const sess = this.filtered[this.cursorIndex];
461
+ if (!sess) return;
462
+ const parsed = parseCommand(this._computeNextStepCommand(sess));
463
+ if (parsed) this.pendingCommand = parsed;
464
+ }
465
+
395
466
  private _promptDelete(): void {
396
467
  const sess = this.filtered[this.cursorIndex];
397
468
  if (!sess) return;
@@ -1,16 +1,13 @@
1
1
  /**
2
- * Session maintenance types and local evaluator.
2
+ * Session maintenance types.
3
3
  *
4
4
  * The canonical evaluator is the SDK version exported from @/runtime/index.ts
5
5
  * (via operations.evaluateSessionMaintenance), which reads from configManager.
6
- *
7
- * This module provides:
8
- * - The shared type surface used across TUI panels.
9
- * - A thin local evaluator kept in sync with the SDK signature so panel code
10
- * that passes configManager has a coherent call site.
6
+ * Import the evaluator from there — this module provides only the shared
7
+ * Panel* type surface used across TUI panels and tests; it intentionally does
8
+ * not re-implement the evaluator.
11
9
  */
12
10
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
13
- import { computeContextUsage } from '../core/context-usage.ts';
14
11
 
15
12
  export type PanelGuidanceMode = 'off' | 'minimal' | 'guided';
16
13
  export type PanelSessionMaintenanceLevel = 'stable' | 'watch' | 'suggest-compact' | 'compacting' | 'needs-repair' | 'unknown';
@@ -59,118 +56,3 @@ export interface PanelSessionMaintenanceStatus {
59
56
  readonly lastCompactedAt?: number;
60
57
  readonly compactRecommended: boolean;
61
58
  }
62
-
63
- /**
64
- * Evaluate session maintenance from config-driven thresholds.
65
- *
66
- * behavior.autoCompactThreshold (percent integer, SDK schema range [10, 100], default 80):
67
- * - [10, 100] → threshold at that percent; autoCompactEnabled = true.
68
- * - 0 (defensive fallback for null/missing config only; not a valid schema value).
69
- *
70
- * NOTE: The SDK's evaluateSessionMaintenance (from @/runtime/index.ts) is the
71
- * canonical implementation used in production. This local version exists so
72
- * panel tests can import types without crossing the SDK boundary.
73
- */
74
- export function evaluateSessionMaintenance(input: PanelSessionMaintenanceInput): PanelSessionMaintenanceStatus {
75
- const guidanceMode: PanelGuidanceMode = (input.configManager.get('behavior.guidanceMode') as PanelGuidanceMode | undefined) ?? 'minimal';
76
- const rawThreshold = Number(input.configManager.get('behavior.autoCompactThreshold') ?? 0);
77
- const thresholdPct = Math.max(0, Number.isFinite(rawThreshold) ? rawThreshold : 0);
78
- const autoCompactEnabled = thresholdPct > 0;
79
-
80
- const { pct: usagePct, remaining: remainingTokens } = computeContextUsage(input.currentTokens, input.contextWindow);
81
- const sessionMemoryCount = Math.max(0, input.sessionMemoryCount ?? 0);
82
- const compactionCount = Math.max(0, input.session?.lineage?.filter((entry) => entry.branchReason === 'compaction').length ?? 0);
83
- const lastCompactedAt = input.session?.lastCompactedAt;
84
- const messageCount = Math.max(0, input.messageCount ?? 0);
85
- const staleByMessageGrowth = (input.session?.compactionMessageCount ?? 0) > 0
86
- ? messageCount - (input.session?.compactionMessageCount ?? 0) >= 12
87
- : messageCount >= 24;
88
-
89
- if (input.contextWindow <= 0) {
90
- return {
91
- level: 'unknown',
92
- summary: 'Context window unavailable.',
93
- reasons: ['Current model does not expose a known context limit yet.'],
94
- nextSteps: ['/provider', '/context'],
95
- guidanceMode,
96
- usagePct,
97
- remainingTokens,
98
- thresholdPct,
99
- autoCompactEnabled,
100
- sessionMemoryCount,
101
- compactionCount,
102
- lastCompactedAt,
103
- compactRecommended: false,
104
- };
105
- }
106
-
107
- if (input.session?.compactionState === 'failed') {
108
- return {
109
- level: 'needs-repair',
110
- summary: 'Compaction needs operator repair.',
111
- reasons: ['Compaction failed and the session may need manual recovery.'],
112
- nextSteps: ['/compact', '/health review'],
113
- guidanceMode,
114
- usagePct,
115
- remainingTokens,
116
- thresholdPct,
117
- autoCompactEnabled,
118
- sessionMemoryCount,
119
- compactionCount,
120
- lastCompactedAt,
121
- compactRecommended: true,
122
- };
123
- }
124
-
125
- const reasons: string[] = [];
126
- const nextSteps: string[] = [];
127
- let summary = 'Session maintenance is stable.';
128
- let compactRecommended = false;
129
- let level: PanelSessionMaintenanceLevel = 'stable';
130
-
131
- const atThreshold = autoCompactEnabled ? usagePct >= thresholdPct : usagePct >= 80;
132
- if (atThreshold || remainingTokens <= 15_000) {
133
- level = 'suggest-compact';
134
- summary = `Compact now to recover context headroom (${usagePct}% used).`;
135
- reasons.push(`Context pressure is high at ${usagePct}% usage.`);
136
- nextSteps.push('/compact', '/panel tokens');
137
- compactRecommended = true;
138
- } else if (usagePct >= Math.max(70, autoCompactEnabled ? thresholdPct - 10 : 70) || staleByMessageGrowth) {
139
- level = 'watch';
140
- summary = staleByMessageGrowth
141
- ? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
142
- : `Watch context growth (${usagePct}% used, threshold ${thresholdPct}%).`;
143
- reasons.push(staleByMessageGrowth
144
- ? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
145
- : `Context usage is climbing toward the ${thresholdPct > 0 ? `${thresholdPct}% auto-compact threshold` : 'maintenance band'}.`);
146
- nextSteps.push('/panel tokens');
147
- } else {
148
- reasons.push('Context pressure is currently within the stable operating band.');
149
- }
150
-
151
- if (sessionMemoryCount > 0) {
152
- reasons.push(`${sessionMemoryCount} pinned session memor${sessionMemoryCount === 1 ? 'y is' : 'ies are'} preserved during compaction.`);
153
- }
154
- if (compactionCount > 0) {
155
- reasons.push(`Last compaction ran ${lastCompactedAt ? new Date(lastCompactedAt).toISOString() : 'recently'}.`);
156
- }
157
- if (!autoCompactEnabled) {
158
- reasons.push('Auto-compaction is disabled; maintenance stays fully manual.');
159
- }
160
-
161
- return {
162
- level,
163
- summary,
164
- reasons,
165
- nextSteps,
166
- guidanceMode,
167
- usagePct,
168
- remainingTokens,
169
- thresholdPct,
170
- autoCompactEnabled,
171
- sessionMemoryCount,
172
- compactionCount,
173
- lastCompactedAt,
174
- compactRecommended,
175
- };
176
- }