@pellux/goodvibes-tui 0.26.0 → 0.28.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 +40 -0
- package/README.md +12 -7
- package/docs/foundation-artifacts/operator-contract.json +4 -1
- package/package.json +2 -2
- package/src/cli/bundle-command.ts +2 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +53 -8
- package/src/core/session-recovery.ts +26 -18
- package/src/core/system-message-router.ts +42 -26
- package/src/core/turn-event-wiring.ts +13 -16
- package/src/daemon/handlers/calendar/caldav-client.ts +2 -1
- package/src/daemon/handlers/inbox/index.ts +2 -1
- package/src/daemon/handlers/inbox/poller.ts +2 -1
- package/src/daemon/handlers/inbox/providers/discord.ts +2 -1
- package/src/daemon/handlers/inbox/providers/email.ts +2 -1
- package/src/daemon/handlers/inbox/providers/route-util.ts +2 -1
- package/src/daemon/handlers/inbox/providers/slack.ts +2 -1
- package/src/daemon/handlers/triage/integration.ts +2 -1
- package/src/daemon/handlers/triage/pipeline.ts +2 -1
- package/src/daemon/handlers/triage/tagger/discord.ts +2 -1
- package/src/daemon/handlers/triage/tagger/imap.ts +4 -3
- package/src/export/gist-uploader.ts +3 -1
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/cloudflare-runtime.ts +2 -1
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/guidance-runtime.ts +2 -4
- package/src/input/commands/health-runtime.ts +13 -7
- package/src/input/commands/knowledge.ts +2 -1
- package/src/input/commands/runtime-services.ts +40 -10
- package/src/input/handler-command-route.ts +1 -1
- package/src/input/handler-feed-routes.ts +61 -4
- package/src/input/handler-feed.ts +13 -0
- package/src/input/handler-interactions.ts +2 -1
- package/src/input/handler-modal-routes.ts +2 -1
- package/src/input/handler-onboarding-cloudflare.ts +2 -1
- package/src/input/handler-onboarding.ts +8 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-ui-state.ts +5 -1
- package/src/input/keybindings.ts +5 -0
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +10 -0
- package/src/input/tts-settings-actions.ts +2 -1
- package/src/main.ts +52 -60
- package/src/panels/agent-inspector-panel.ts +90 -15
- package/src/panels/agent-inspector-shared.ts +3 -3
- package/src/panels/agent-logs-panel.ts +149 -72
- package/src/panels/approval-panel.ts +146 -95
- package/src/panels/automation-control-panel.ts +24 -1
- package/src/panels/base-panel.ts +28 -3
- package/src/panels/builtin/agent.ts +3 -1
- package/src/panels/builtin/development.ts +2 -1
- package/src/panels/builtin/session.ts +1 -1
- package/src/panels/cockpit-panel.ts +24 -5
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +108 -43
- package/src/panels/context-visualizer-panel.ts +49 -15
- package/src/panels/control-plane-panel.ts +27 -1
- package/src/panels/cost-tracker-panel.ts +87 -65
- package/src/panels/debug-panel.ts +61 -37
- package/src/panels/diff-panel.ts +59 -30
- package/src/panels/docs-panel.ts +30 -24
- package/src/panels/eval-panel.ts +130 -81
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +42 -42
- package/src/panels/file-preview-panel.ts +11 -3
- package/src/panels/forensics-panel.ts +27 -13
- package/src/panels/git-panel.ts +65 -84
- package/src/panels/hooks-panel.ts +36 -2
- package/src/panels/incident-review-panel.ts +31 -10
- package/src/panels/intelligence-panel.ts +152 -71
- package/src/panels/knowledge-graph-panel.ts +89 -27
- package/src/panels/local-auth-panel.ts +17 -11
- package/src/panels/marketplace-panel.ts +33 -13
- package/src/panels/memory-panel.ts +7 -5
- package/src/panels/ops-control-panel.ts +69 -7
- package/src/panels/ops-strategy-panel.ts +61 -43
- package/src/panels/orchestration-panel.ts +34 -4
- package/src/panels/panel-list-panel.ts +33 -8
- package/src/panels/panel-manager.ts +23 -4
- package/src/panels/plan-dashboard-panel.ts +183 -66
- package/src/panels/plugins-panel.ts +48 -10
- package/src/panels/policy-panel.ts +60 -23
- package/src/panels/polish-core.ts +157 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +44 -145
- package/src/panels/project-planning-panel.ts +27 -4
- package/src/panels/provider-accounts-panel.ts +55 -18
- package/src/panels/provider-health-panel.ts +118 -87
- package/src/panels/provider-stats-panel.ts +47 -22
- package/src/panels/qr-panel.ts +23 -11
- package/src/panels/remote-panel.ts +12 -5
- package/src/panels/routes-panel.ts +27 -5
- package/src/panels/sandbox-panel.ts +62 -27
- package/src/panels/schedule-panel.ts +44 -24
- package/src/panels/scrollable-list-panel.ts +124 -10
- package/src/panels/security-panel.ts +72 -20
- package/src/panels/services-panel.ts +68 -22
- package/src/panels/session-browser-panel.ts +42 -26
- package/src/panels/session-maintenance.ts +2 -2
- package/src/panels/settings-sync-panel.ts +30 -15
- package/src/panels/skills-panel.ts +2 -1
- package/src/panels/subscription-panel.ts +21 -3
- package/src/panels/symbol-outline-panel.ts +40 -47
- package/src/panels/system-messages-panel.ts +60 -27
- package/src/panels/tasks-panel.ts +36 -14
- package/src/panels/thinking-panel.ts +32 -36
- package/src/panels/token-budget-panel.ts +80 -53
- package/src/panels/tool-inspector-panel.ts +37 -39
- package/src/panels/types.ts +25 -0
- package/src/panels/watchers-panel.ts +25 -5
- package/src/panels/work-plan-panel.ts +127 -35
- package/src/panels/worktree-panel.ts +65 -20
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +53 -147
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/modal-utils.ts +0 -10
- package/src/renderer/model-picker-overlay.ts +9 -4
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-modal.ts +2 -2
- package/src/renderer/progress.ts +3 -2
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/ui-factory.ts +4 -6
- package/src/runtime/bootstrap-command-context.ts +3 -0
- package/src/runtime/bootstrap-command-parts.ts +3 -1
- package/src/runtime/bootstrap-core.ts +31 -31
- package/src/runtime/bootstrap-shell.ts +1 -0
- package/src/runtime/onboarding/apply.ts +4 -3
- package/src/runtime/onboarding/snapshot.ts +4 -3
- package/src/runtime/process-lifecycle.ts +195 -0
- package/src/runtime/services.ts +4 -1
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/runtime/wrfc-persistence.ts +20 -5
- package/src/shell/ui-openers.ts +3 -2
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/verification/live-verifier.ts +4 -3
- package/src/version.ts +1 -1
- package/src/core/context-auto-compact.ts +0 -110
- package/src/panels/panel-picker.ts +0 -106
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
+
import { fitDisplay } from '../utils/terminal-width.ts';
|
|
3
4
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
5
|
import type { KeyName } from './types.ts';
|
|
5
6
|
import type { ProviderSubscription, PendingSubscriptionLogin } from '@pellux/goodvibes-sdk/platform/config';
|
|
@@ -9,6 +10,7 @@ import type { ServiceInspectionQuery, SubscriptionAccessQuery } from '../runtime
|
|
|
9
10
|
import {
|
|
10
11
|
buildEmptyState,
|
|
11
12
|
buildGuidanceLine,
|
|
13
|
+
buildKeyboardHints,
|
|
12
14
|
buildKeyValueLine,
|
|
13
15
|
buildPanelListRow,
|
|
14
16
|
buildPanelLine,
|
|
@@ -94,7 +96,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
|
|
|
94
96
|
protected renderItem(row: SubscriptionRow, index: number, selected: boolean, width: number): Line {
|
|
95
97
|
const status = statusOf(row);
|
|
96
98
|
return buildPanelListRow(width, [
|
|
97
|
-
{ text: row.provider
|
|
99
|
+
{ text: fitDisplay(row.provider, 16), fg: C.value },
|
|
98
100
|
{ text: ` ${status.toUpperCase().padEnd(12)}`, fg: statusColor(status) },
|
|
99
101
|
{ text: ` oauth=${row.hasOAuthConfig ? 'yes' : 'no'} `, fg: row.hasOAuthConfig ? C.info : C.dim },
|
|
100
102
|
{ text: ` override=${row.subscription ? 'active' : 'off'}`, fg: row.subscription ? C.good : C.dim },
|
|
@@ -177,6 +179,22 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
|
|
|
177
179
|
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.rows.length - 1));
|
|
178
180
|
}
|
|
179
181
|
|
|
182
|
+
/** Footer hints that reflect the current view: confirm-pending vs browsing. */
|
|
183
|
+
private buildFooterHint(width: number): Line {
|
|
184
|
+
if (this.confirm) {
|
|
185
|
+
return buildKeyboardHints(width, [
|
|
186
|
+
{ keys: 'y/Enter', label: 'confirm sign out' },
|
|
187
|
+
{ keys: 'n/Esc', label: 'cancel' },
|
|
188
|
+
], C);
|
|
189
|
+
}
|
|
190
|
+
const selected = this.rows[this.selectedIndex];
|
|
191
|
+
const hints: Array<{ keys: string; label: string }> = [{ keys: 'Up/Down', label: 'select' }];
|
|
192
|
+
if (selected?.subscription) hints.push({ keys: 'Enter', label: 'sign out' });
|
|
193
|
+
else if (selected?.hasOAuthConfig) hints.push({ keys: '/subscription login <p> start', label: 'sign in' });
|
|
194
|
+
hints.push({ keys: 'r', label: 'refresh' });
|
|
195
|
+
return buildKeyboardHints(width, hints, C);
|
|
196
|
+
}
|
|
197
|
+
|
|
180
198
|
public render(width: number, height: number): Line[] {
|
|
181
199
|
this.refresh();
|
|
182
200
|
this.clampSelection();
|
|
@@ -214,7 +232,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
|
|
|
214
232
|
sections: [{ lines: [...summaryLines, ...emptyLines] }],
|
|
215
233
|
footerLines: [
|
|
216
234
|
buildGuidanceLine(width, '/subscription login <provider> start', 'start browser-based provider login from the packaged subscription surface', C),
|
|
217
|
-
|
|
235
|
+
this.buildFooterHint(width),
|
|
218
236
|
],
|
|
219
237
|
palette: C,
|
|
220
238
|
});
|
|
@@ -264,7 +282,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
|
|
|
264
282
|
footer: [
|
|
265
283
|
...detailRows,
|
|
266
284
|
buildGuidanceLine(width, '/subscription login <provider> start', 'start browser-based provider login from the packaged subscription surface', C),
|
|
267
|
-
|
|
285
|
+
this.buildFooterHint(width),
|
|
268
286
|
],
|
|
269
287
|
});
|
|
270
288
|
}
|
|
@@ -3,13 +3,14 @@ import { createEmptyLine } from '../types/grid.ts';
|
|
|
3
3
|
import type { Line } from '../types/grid.ts';
|
|
4
4
|
import {
|
|
5
5
|
buildEmptyState,
|
|
6
|
+
buildKeyboardHints,
|
|
6
7
|
buildPanelLine,
|
|
7
|
-
|
|
8
|
+
buildTreeRow,
|
|
8
9
|
buildPanelWorkspace,
|
|
9
10
|
resolveScrollablePanelSection,
|
|
10
11
|
DEFAULT_PANEL_PALETTE,
|
|
12
|
+
extendPalette,
|
|
11
13
|
} from './polish.ts';
|
|
12
|
-
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
13
14
|
|
|
14
15
|
// ── Symbol types ────────────────────────────────────────────────────────────
|
|
15
16
|
|
|
@@ -25,6 +26,10 @@ export interface SymbolEntry {
|
|
|
25
26
|
|
|
26
27
|
// ── Rendering constants ──────────────────────────────────────────────────────
|
|
27
28
|
|
|
29
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
30
|
+
selectedBg: '236',
|
|
31
|
+
});
|
|
32
|
+
|
|
28
33
|
/** ANSI 256-color fg codes per symbol kind. */
|
|
29
34
|
const KIND_COLORS: Record<SymbolKind, string> = {
|
|
30
35
|
function: '87', // cyan
|
|
@@ -36,15 +41,15 @@ const KIND_COLORS: Record<SymbolKind, string> = {
|
|
|
36
41
|
const: '245', // grey
|
|
37
42
|
};
|
|
38
43
|
|
|
39
|
-
/**
|
|
40
|
-
const
|
|
41
|
-
function: '
|
|
42
|
-
method: '
|
|
43
|
-
class: '
|
|
44
|
-
namespace: '
|
|
45
|
-
interface: '
|
|
46
|
-
type: '
|
|
47
|
-
const: '
|
|
44
|
+
/** Single-char type icon for the tree row (kept ASCII for column safety). */
|
|
45
|
+
const KIND_ICONS: Record<SymbolKind, string> = {
|
|
46
|
+
function: 'ƒ',
|
|
47
|
+
method: 'ƒ',
|
|
48
|
+
class: 'C',
|
|
49
|
+
namespace: 'N',
|
|
50
|
+
interface: 'I',
|
|
51
|
+
type: 'T',
|
|
52
|
+
const: 'k',
|
|
48
53
|
};
|
|
49
54
|
|
|
50
55
|
/** Regex patterns to extract symbols. Each produces named groups: kind, name, line. */
|
|
@@ -188,8 +193,10 @@ export class SymbolOutlinePanel extends BasePanel {
|
|
|
188
193
|
this.currentPath
|
|
189
194
|
? 'The current file did not produce outline entries with the lightweight parser heuristics.'
|
|
190
195
|
: 'Load a file in the preview panel to populate its outline here.',
|
|
191
|
-
|
|
192
|
-
|
|
196
|
+
this.currentPath
|
|
197
|
+
? []
|
|
198
|
+
: [{ command: '/explorer', summary: 'pick a file in the explorer, then Enter to preview and outline it' }],
|
|
199
|
+
C,
|
|
193
200
|
),
|
|
194
201
|
},
|
|
195
202
|
],
|
|
@@ -201,7 +208,7 @@ export class SymbolOutlinePanel extends BasePanel {
|
|
|
201
208
|
const outlineSection = resolveScrollablePanelSection(width, height, {
|
|
202
209
|
intro: this.currentPath ? this.currentPath : 'Outline the current file into navigable symbols and lightweight parent/child structure.',
|
|
203
210
|
footerLines: [
|
|
204
|
-
|
|
211
|
+
buildKeyboardHints(width, [{ keys: '↑/↓', label: 'navigate' }, { keys: 'Space/←/→', label: 'collapse' }, { keys: 'Enter', label: 'jump to source →' }], DEFAULT_PANEL_PALETTE),
|
|
205
212
|
],
|
|
206
213
|
palette: DEFAULT_PANEL_PALETTE,
|
|
207
214
|
beforeSections: [
|
|
@@ -291,7 +298,7 @@ export class SymbolOutlinePanel extends BasePanel {
|
|
|
291
298
|
},
|
|
292
299
|
],
|
|
293
300
|
footerLines: [
|
|
294
|
-
|
|
301
|
+
buildKeyboardHints(width, [{ keys: '↑/↓', label: 'navigate' }, { keys: 'Space/←/→', label: 'collapse' }, { keys: 'Enter', label: 'jump to source →' }], DEFAULT_PANEL_PALETTE),
|
|
295
302
|
],
|
|
296
303
|
palette: DEFAULT_PANEL_PALETTE,
|
|
297
304
|
});
|
|
@@ -434,10 +441,7 @@ function buildVisibleRows(symbols: SymbolEntry[], collapsed: Set<string>): Visib
|
|
|
434
441
|
return rows;
|
|
435
442
|
}
|
|
436
443
|
|
|
437
|
-
/**
|
|
438
|
-
* Write a string into a Line starting at column x, applying fg/bg/style.
|
|
439
|
-
*/
|
|
440
|
-
/** Render a container header row (class / namespace). */
|
|
444
|
+
/** Render a container header row (class / namespace) via the shared tree row. */
|
|
441
445
|
function _renderHeader(
|
|
442
446
|
width: number,
|
|
443
447
|
row: Extract<VisibleRow, { kind: 'header' }>,
|
|
@@ -445,24 +449,19 @@ function _renderHeader(
|
|
|
445
449
|
bgColor: string,
|
|
446
450
|
collapsed: Set<string>,
|
|
447
451
|
): Line {
|
|
448
|
-
// Collapse indicator
|
|
449
452
|
const isCollapsed = collapsed.has(row.name);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
{ text:
|
|
458
|
-
|
|
459
|
-
{ text: row.name, fg: isSelected ? '255' : '252', bold: isSelected },
|
|
460
|
-
{ text: ' '.repeat(gap), fg: DEFAULT_PANEL_PALETTE.dim },
|
|
461
|
-
{ text: lineNumStr, fg: DEFAULT_PANEL_PALETTE.dim },
|
|
462
|
-
], { selected: isSelected, selectedBg: bgColor, fillFg: isSelected ? '255' : '' });
|
|
453
|
+
return buildTreeRow(width, {
|
|
454
|
+
depth: 0,
|
|
455
|
+
label: row.name,
|
|
456
|
+
icon: KIND_ICONS[row.symbolKind],
|
|
457
|
+
expandable: row.hasChildren,
|
|
458
|
+
expanded: !isCollapsed,
|
|
459
|
+
labelColor: KIND_COLORS[row.symbolKind],
|
|
460
|
+
metadata: [{ text: `:${row.line}`, fg: DEFAULT_PANEL_PALETTE.dim }],
|
|
461
|
+
}, C, { selected: isSelected, selectedBg: bgColor || C.selectedBg });
|
|
463
462
|
}
|
|
464
463
|
|
|
465
|
-
/** Render a regular symbol row. */
|
|
464
|
+
/** Render a regular symbol row via the shared tree row. */
|
|
466
465
|
function _renderSymbol(
|
|
467
466
|
width: number,
|
|
468
467
|
row: Extract<VisibleRow, { kind: 'symbol' }>,
|
|
@@ -470,17 +469,11 @@ function _renderSymbol(
|
|
|
470
469
|
bgColor: string,
|
|
471
470
|
): Line {
|
|
472
471
|
const { symbol, depth } = row;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
{ text: kindLabel, fg: KIND_COLORS[symbol.kind] },
|
|
481
|
-
{ text: ' ', fg: isSelected ? '255' : '251' },
|
|
482
|
-
{ text: symbol.name, fg: isSelected ? '255' : '251', bold: isSelected },
|
|
483
|
-
{ text: ' '.repeat(gap), fg: DEFAULT_PANEL_PALETTE.dim },
|
|
484
|
-
{ text: lineNumStr, fg: DEFAULT_PANEL_PALETTE.dim },
|
|
485
|
-
], { selected: isSelected, selectedBg: bgColor, fillFg: isSelected ? '255' : '' });
|
|
472
|
+
return buildTreeRow(width, {
|
|
473
|
+
depth,
|
|
474
|
+
label: symbol.name,
|
|
475
|
+
icon: KIND_ICONS[symbol.kind],
|
|
476
|
+
labelColor: KIND_COLORS[symbol.kind],
|
|
477
|
+
metadata: [{ text: `:${symbol.line}`, fg: DEFAULT_PANEL_PALETTE.dim }],
|
|
478
|
+
}, C, { selected: isSelected, selectedBg: bgColor || C.selectedBg });
|
|
486
479
|
}
|
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
buildEmptyState,
|
|
15
15
|
buildGuidanceLine,
|
|
16
16
|
buildKeyValueLine,
|
|
17
|
+
buildKeyboardHints,
|
|
17
18
|
buildPanelListRow,
|
|
18
19
|
buildPanelLine,
|
|
20
|
+
buildStatusBadge,
|
|
19
21
|
buildSummaryBlock,
|
|
20
22
|
buildPanelWorkspace,
|
|
21
23
|
resolvePrimaryScrollableSection,
|
|
@@ -59,6 +61,12 @@ export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry>
|
|
|
59
61
|
constructor(configManager: ConfigManager, componentHealthMonitor?: ComponentHealthMonitor) {
|
|
60
62
|
super('system-messages', 'System Messages', 'J', 'monitoring', componentHealthMonitor);
|
|
61
63
|
this.configManager = configManager;
|
|
64
|
+
this.filterEnabled = true;
|
|
65
|
+
this.filterLabel = 'Filter messages';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected override filterMatches(entry: SystemMessageEntry, q: string): boolean {
|
|
69
|
+
return entry.text.toLowerCase().includes(q) || entry.priority.toLowerCase().includes(q);
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
// ---------------------------------------------------------------------------
|
|
@@ -79,7 +87,7 @@ export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry>
|
|
|
79
87
|
return buildPanelListRow(width, [
|
|
80
88
|
{ text: `${fmtTime(entry.ts)} `, fg: C.ts },
|
|
81
89
|
{
|
|
82
|
-
text: `${entry.priority === 'high' ? 'HIGH' : 'LOW
|
|
90
|
+
text: `${(entry.priority === 'high' ? 'HIGH' : 'LOW').padEnd(4)} `,
|
|
83
91
|
fg: entry.priority === 'high' ? C.high : C.low,
|
|
84
92
|
bold: entry.priority === 'high',
|
|
85
93
|
},
|
|
@@ -129,6 +137,23 @@ export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry>
|
|
|
129
137
|
// Render — multi-section layout (posture + list + detail)
|
|
130
138
|
// ---------------------------------------------------------------------------
|
|
131
139
|
|
|
140
|
+
// Context-aware footer: navigation keys plus filter keys that reflect the
|
|
141
|
+
// current filter state (active typing / applied query / inactive).
|
|
142
|
+
private footerHints(): Array<{ keys: string; label: string }> {
|
|
143
|
+
const hints: Array<{ keys: string; label: string }> = [
|
|
144
|
+
{ keys: 'j/k', label: 'scroll' },
|
|
145
|
+
{ keys: 'g/G', label: 'jump' },
|
|
146
|
+
];
|
|
147
|
+
if (this.filterActive) {
|
|
148
|
+
hints.push({ keys: 'Esc', label: 'clear filter' });
|
|
149
|
+
} else if (this.filterQuery) {
|
|
150
|
+
hints.push({ keys: '/', label: 'edit filter' }, { keys: 'Esc', label: 'clear filter' });
|
|
151
|
+
} else {
|
|
152
|
+
hints.push({ keys: '/', label: 'filter' });
|
|
153
|
+
}
|
|
154
|
+
return hints;
|
|
155
|
+
}
|
|
156
|
+
|
|
132
157
|
override render(width: number, height: number): Line[] {
|
|
133
158
|
return this.trackedRender(() => {
|
|
134
159
|
const intro = 'Operational system traffic routed out of the main conversation to reduce noise and keep runtime status reviewable.';
|
|
@@ -151,7 +176,7 @@ export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry>
|
|
|
151
176
|
),
|
|
152
177
|
}],
|
|
153
178
|
footerLines: [
|
|
154
|
-
buildPanelLine(width, [['
|
|
179
|
+
buildPanelLine(width, [[' Low-priority system traffic lands here by default. Routing is configurable via /settings.', C.dim]]),
|
|
155
180
|
],
|
|
156
181
|
palette: C,
|
|
157
182
|
});
|
|
@@ -162,12 +187,16 @@ export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry>
|
|
|
162
187
|
const lowCount = this._messages.length - highCount;
|
|
163
188
|
this.selectedIndex = Math.min(this.selectedIndex, this._messages.length - 1);
|
|
164
189
|
const ui = this.configManager.getRaw().ui;
|
|
190
|
+
const latest = this._messages[this._messages.length - 1];
|
|
165
191
|
const postureLines = [
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
192
|
+
// Severity + recency first: high-priority count leads, newest message age follows.
|
|
193
|
+
buildPanelLine(width, [
|
|
194
|
+
[' ', C.label],
|
|
195
|
+
...buildStatusBadge(highCount > 0 ? 'failed' : 'completed', 'high', { count: highCount }),
|
|
196
|
+
[' ', C.dim],
|
|
197
|
+
...buildStatusBadge('review', 'low', { count: lowCount }),
|
|
198
|
+
...(latest ? ([[' newest ', C.label], [`${fmtTime(latest.ts)}`, C.value]] as Array<[string, string]>) : []),
|
|
199
|
+
]),
|
|
171
200
|
buildKeyValueLine(width, [
|
|
172
201
|
{ label: 'system route', value: ui.systemMessages, valueColor: C.info },
|
|
173
202
|
{ label: 'ops route', value: ui.operationalMessages, valueColor: C.info },
|
|
@@ -176,28 +205,33 @@ export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry>
|
|
|
176
205
|
buildGuidanceLine(width, '/settings', 'adjust where operational and WRFC messages render across panels and conversation', C),
|
|
177
206
|
];
|
|
178
207
|
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
208
|
+
const visible = this.getVisibleItems();
|
|
209
|
+
this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, visible.length - 1));
|
|
210
|
+
const selected = visible[this.selectedIndex];
|
|
211
|
+
const messageRows: Line[] = visible.length > 0
|
|
212
|
+
? visible.map((entry, index) => this.renderItem(entry, index, index === this.selectedIndex, width))
|
|
213
|
+
: [buildPanelLine(width, [[` No messages match "${this.filterQuery.trim()}" (Esc to clear)`, C.dim]])];
|
|
183
214
|
|
|
215
|
+
const filterSection: PanelWorkspaceSection = { lines: [this.buildFilterLine(width)] };
|
|
184
216
|
const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'System posture', postureLines, C) };
|
|
185
|
-
const detailSection: PanelWorkspaceSection =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
217
|
+
const detailSection: PanelWorkspaceSection = selected
|
|
218
|
+
? {
|
|
219
|
+
title: 'Selected Message',
|
|
220
|
+
lines: [
|
|
221
|
+
buildPanelLine(width, [
|
|
222
|
+
[' Time ', C.label],
|
|
223
|
+
[fmtTime(selected.ts), C.value],
|
|
224
|
+
[' Priority ', C.label],
|
|
225
|
+
[selected.priority, selected.priority === 'high' ? C.high : C.low],
|
|
226
|
+
]),
|
|
227
|
+
...buildBodyText(width, selected.text, C, C.value),
|
|
228
|
+
],
|
|
229
|
+
}
|
|
230
|
+
: { title: 'Selected Message', lines: [] };
|
|
197
231
|
const messagesSection = resolvePrimaryScrollableSection(width, height, {
|
|
198
232
|
intro,
|
|
199
233
|
palette: C,
|
|
200
|
-
beforeSections: [postureSection],
|
|
234
|
+
beforeSections: [filterSection, postureSection],
|
|
201
235
|
section: {
|
|
202
236
|
title: 'Timeline',
|
|
203
237
|
scrollableLines: messageRows,
|
|
@@ -210,6 +244,7 @@ export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry>
|
|
|
210
244
|
});
|
|
211
245
|
this.scrollStart = messagesSection.scrollOffset;
|
|
212
246
|
const sections: PanelWorkspaceSection[] = [
|
|
247
|
+
filterSection,
|
|
213
248
|
postureSection,
|
|
214
249
|
messagesSection.section,
|
|
215
250
|
detailSection,
|
|
@@ -219,9 +254,7 @@ export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry>
|
|
|
219
254
|
title: 'System Messages',
|
|
220
255
|
intro,
|
|
221
256
|
sections,
|
|
222
|
-
footerLines: [
|
|
223
|
-
buildPanelLine(width, [[' j/k or Up/Down scroll PgUp/PgDn page g/G jump', C.dim]]),
|
|
224
|
-
],
|
|
257
|
+
footerLines: [buildKeyboardHints(width, this.footerHints(), C)],
|
|
225
258
|
palette: C,
|
|
226
259
|
});
|
|
227
260
|
return lines;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
+
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
4
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
5
|
import type { RuntimeTask, TaskLifecycleState } from '@/runtime/index.ts';
|
|
5
6
|
import type { ManagedWorktreeMeta } from '@/runtime/index.ts';
|
|
@@ -8,12 +9,14 @@ import {
|
|
|
8
9
|
buildDetailBlock,
|
|
9
10
|
buildEmptyState,
|
|
10
11
|
buildGuidanceLine,
|
|
12
|
+
buildKeyboardHints,
|
|
11
13
|
buildPanelListRow,
|
|
12
14
|
buildPanelLine,
|
|
13
15
|
buildSummaryBlock,
|
|
14
16
|
buildPanelWorkspace,
|
|
15
17
|
DEFAULT_PANEL_PALETTE,
|
|
16
18
|
} from './polish.ts';
|
|
19
|
+
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
17
20
|
|
|
18
21
|
const C = {
|
|
19
22
|
...DEFAULT_PANEL_PALETTE,
|
|
@@ -35,13 +38,7 @@ function formatWhen(value?: number): string {
|
|
|
35
38
|
|
|
36
39
|
function formatDuration(startedAt?: number, endedAt?: number): string {
|
|
37
40
|
if (!startedAt) return 'n/a';
|
|
38
|
-
|
|
39
|
-
const ms = Math.max(0, end - startedAt);
|
|
40
|
-
if (ms < 1_000) return `${ms}ms`;
|
|
41
|
-
if (ms < 60_000) return `${(ms / 1_000).toFixed(ms < 10_000 ? 1 : 0)}s`;
|
|
42
|
-
const mins = Math.floor(ms / 60_000);
|
|
43
|
-
const secs = Math.floor((ms % 60_000) / 1_000);
|
|
44
|
-
return `${mins}m ${secs}s`;
|
|
41
|
+
return formatElapsed(Math.max(0, (endedAt ?? Date.now()) - startedAt));
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
function kindLabel(kind: RuntimeTask['kind']): string {
|
|
@@ -157,6 +154,8 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
157
154
|
) {
|
|
158
155
|
super('tasks', 'Tasks', 'J', 'monitoring');
|
|
159
156
|
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
157
|
+
this.filterEnabled = true;
|
|
158
|
+
this.filterLabel = 'Filter tasks';
|
|
160
159
|
this.readModel = readModel;
|
|
161
160
|
this.worktrees = worktrees;
|
|
162
161
|
this.unsubscribers = [
|
|
@@ -183,6 +182,13 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
183
182
|
];
|
|
184
183
|
}
|
|
185
184
|
|
|
185
|
+
protected override filterMatches(task: RuntimeTask, q: string): boolean {
|
|
186
|
+
return task.title.toLowerCase().includes(q)
|
|
187
|
+
|| task.status.toLowerCase().includes(q)
|
|
188
|
+
|| task.id.toLowerCase().includes(q)
|
|
189
|
+
|| String(task.kind).toLowerCase().includes(q);
|
|
190
|
+
}
|
|
191
|
+
|
|
186
192
|
protected getItems(): readonly RuntimeTask[] {
|
|
187
193
|
if (!this.readModel) return [];
|
|
188
194
|
return sortTasks([...this.readModel.getSnapshot().tasks]);
|
|
@@ -193,7 +199,7 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
193
199
|
{ text: task.status.padEnd(10), fg: statusColor(task.status) },
|
|
194
200
|
{ text: ` ${kindLabel(task.kind).padEnd(12)}`, fg: C.value },
|
|
195
201
|
{ text: ` ${task.id.slice(0, 8)} `, fg: C.dim },
|
|
196
|
-
{ text: task.title
|
|
202
|
+
{ text: truncateDisplay(task.title, Math.max(0, width - 37)), fg: C.value },
|
|
197
203
|
], C, { selected });
|
|
198
204
|
}
|
|
199
205
|
|
|
@@ -204,7 +210,7 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
204
210
|
return true;
|
|
205
211
|
}
|
|
206
212
|
if (key === 'end') {
|
|
207
|
-
const tasks = this.
|
|
213
|
+
const tasks = this.getVisibleItems();
|
|
208
214
|
this.selectedIndex = Math.max(0, tasks.length - 1);
|
|
209
215
|
this.markDirty();
|
|
210
216
|
return true;
|
|
@@ -215,7 +221,23 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
215
221
|
public render(width: number, height: number): Line[] {
|
|
216
222
|
this.clampSelection();
|
|
217
223
|
const intro = 'Live task lifecycle, ownership, retries, and result/error details across runtime execution domains.';
|
|
218
|
-
const
|
|
224
|
+
const visibleCount = this.getVisibleItems().length;
|
|
225
|
+
// Context-aware footer: position + only the keys that apply in the current
|
|
226
|
+
// (filtering vs browsing) state.
|
|
227
|
+
const footerLines = [
|
|
228
|
+
this.filterActive
|
|
229
|
+
? buildKeyboardHints(width, [
|
|
230
|
+
{ keys: 'type', label: 'filter tasks' },
|
|
231
|
+
{ keys: 'Enter', label: 'apply' },
|
|
232
|
+
{ keys: 'Esc', label: 'clear' },
|
|
233
|
+
], C)
|
|
234
|
+
: buildKeyboardHints(width, [
|
|
235
|
+
{ keys: visibleCount > 0 ? `${this.selectedIndex + 1}/${visibleCount}` : '0/0', label: 'task' },
|
|
236
|
+
{ keys: '↑/↓', label: 'move' },
|
|
237
|
+
{ keys: 'Home/End', label: 'jump' },
|
|
238
|
+
{ keys: '/', label: 'filter' },
|
|
239
|
+
], C),
|
|
240
|
+
];
|
|
219
241
|
|
|
220
242
|
if (!this.readModel) {
|
|
221
243
|
const workspace = buildPanelWorkspace(width, height, {
|
|
@@ -272,7 +294,7 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
272
294
|
[' kind ', C.label],
|
|
273
295
|
[selected.kind, C.value],
|
|
274
296
|
[' owner ', C.label],
|
|
275
|
-
[selected.owner
|
|
297
|
+
[truncateDisplay(selected.owner, Math.max(0, width - 46)), C.dim],
|
|
276
298
|
]));
|
|
277
299
|
}
|
|
278
300
|
postureLines.push(
|
|
@@ -359,7 +381,7 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
359
381
|
]]));
|
|
360
382
|
for (const record of attachedWorktrees.records.slice(0, 2)) {
|
|
361
383
|
detailRows.push(buildPanelLine(width, [[
|
|
362
|
-
` ${record.state.padEnd(15)} ${record.path}
|
|
384
|
+
truncateDisplay(` ${record.state.padEnd(15)} ${record.path}`, Math.max(0, width - 2)),
|
|
363
385
|
record.state === 'active' ? C.running : record.state === 'paused' ? C.blocked : C.dim,
|
|
364
386
|
]]));
|
|
365
387
|
}
|
|
@@ -373,14 +395,14 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
373
395
|
if (selected.error) {
|
|
374
396
|
detailRows.push(buildPanelLine(width, [
|
|
375
397
|
[' Error: ', C.label],
|
|
376
|
-
[selected.error
|
|
398
|
+
[truncateDisplay(selected.error, Math.max(0, width - 10)), C.failed],
|
|
377
399
|
]));
|
|
378
400
|
}
|
|
379
401
|
if (selected.result !== undefined) {
|
|
380
402
|
const resultText = safeJson(selected.result);
|
|
381
403
|
detailRows.push(buildPanelLine(width, [
|
|
382
404
|
[' Result: ', C.label],
|
|
383
|
-
[resultText
|
|
405
|
+
[truncateDisplay(resultText, Math.max(0, width - 11)), C.dim],
|
|
384
406
|
]));
|
|
385
407
|
}
|
|
386
408
|
}
|
|
@@ -12,25 +12,20 @@ import {
|
|
|
12
12
|
buildStyledPanelLine,
|
|
13
13
|
buildPanelWorkspace,
|
|
14
14
|
resolveScrollablePanelSection,
|
|
15
|
+
extendPalette,
|
|
15
16
|
DEFAULT_PANEL_PALETTE,
|
|
16
17
|
type PanelWorkspaceSection,
|
|
17
18
|
} from './polish.ts';
|
|
18
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
19
|
+
import { truncateDisplay, wrapText } from '../utils/terminal-width.ts';
|
|
19
20
|
|
|
20
|
-
const C = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
statusBar: '#222233',
|
|
24
|
-
statusFg: '#aaaaaa',
|
|
21
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
22
|
+
// Domain-specific reasoning tones (purples) and the active-stream cyan have no
|
|
23
|
+
// clean shared equivalent; selection background maps to the shared selectBg.
|
|
25
24
|
reasoningFg: '#aa88ff',
|
|
26
25
|
activeFg: '#cc99ff',
|
|
27
|
-
dimFg: '#555566',
|
|
28
26
|
turnLabel: '#7766bb',
|
|
29
27
|
activeLabel: '#00ffff',
|
|
30
|
-
|
|
31
|
-
selected: '#00ffff',
|
|
32
|
-
selectedBg: '#1a2a3a',
|
|
33
|
-
} as const;
|
|
28
|
+
});
|
|
34
29
|
|
|
35
30
|
interface ReasoningBlock {
|
|
36
31
|
turnId: number;
|
|
@@ -39,23 +34,6 @@ interface ReasoningBlock {
|
|
|
39
34
|
collapsed: boolean;
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
function wrapLines(text: string, width: number): string[] {
|
|
43
|
-
const result: string[] = [];
|
|
44
|
-
const raw = text.split('\n');
|
|
45
|
-
for (const line of raw) {
|
|
46
|
-
if (line.length <= width) {
|
|
47
|
-
result.push(line);
|
|
48
|
-
} else {
|
|
49
|
-
let start = 0;
|
|
50
|
-
while (start < line.length) {
|
|
51
|
-
result.push(line.slice(start, start + width));
|
|
52
|
-
start += width;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
37
|
type FlatRow = { kind: 'header'; blockIndex: number; text: string } | { kind: 'content'; text: string };
|
|
60
38
|
|
|
61
39
|
export class ThinkingPanel extends BasePanel {
|
|
@@ -126,8 +104,8 @@ export class ThinkingPanel extends BasePanel {
|
|
|
126
104
|
lines: buildEmptyState(
|
|
127
105
|
width,
|
|
128
106
|
' No reasoning content yet',
|
|
129
|
-
'When the model emits thinking or reasoning deltas, they accumulate here in expandable blocks.',
|
|
130
|
-
[],
|
|
107
|
+
'When the model emits thinking or reasoning deltas during a turn, they accumulate here in expandable per-turn blocks.',
|
|
108
|
+
[{ command: '/chat <prompt>', summary: 'start a turn with a reasoning-capable model to stream thinking here' }],
|
|
131
109
|
DEFAULT_PANEL_PALETTE,
|
|
132
110
|
),
|
|
133
111
|
},
|
|
@@ -157,12 +135,30 @@ export class ThinkingPanel extends BasePanel {
|
|
|
157
135
|
],
|
|
158
136
|
};
|
|
159
137
|
|
|
160
|
-
|
|
138
|
+
// Resolve the reasoning block that owns the cursor row (walk back to its
|
|
139
|
+
// header) so the detail block describes the actual turn, not just row kind.
|
|
140
|
+
let selectedBlockIndex = -1;
|
|
141
|
+
for (let i = Math.min(this.cursorIndex, flat.length - 1); i >= 0; i--) {
|
|
142
|
+
const row = flat[i];
|
|
143
|
+
if (row?.kind === 'header') { selectedBlockIndex = row.blockIndex; break; }
|
|
144
|
+
}
|
|
145
|
+
const selectedBlock = selectedBlockIndex >= 0 ? this.blocks[selectedBlockIndex] : undefined;
|
|
161
146
|
const selectedSection: PanelWorkspaceSection = {
|
|
162
147
|
title: 'Selected',
|
|
163
|
-
lines:
|
|
164
|
-
|
|
165
|
-
|
|
148
|
+
lines: selectedBlock
|
|
149
|
+
? [
|
|
150
|
+
buildPanelLine(width, [
|
|
151
|
+
[' Turn ', DEFAULT_PANEL_PALETTE.label],
|
|
152
|
+
[String(selectedBlock.turnId), DEFAULT_PANEL_PALETTE.value],
|
|
153
|
+
[' State ', DEFAULT_PANEL_PALETTE.label],
|
|
154
|
+
[selectedBlock.active ? 'streaming' : 'complete', selectedBlock.active ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.good],
|
|
155
|
+
[' View ', DEFAULT_PANEL_PALETTE.label],
|
|
156
|
+
[selectedBlock.collapsed ? 'collapsed' : 'expanded', DEFAULT_PANEL_PALETTE.info],
|
|
157
|
+
[' Chars ', DEFAULT_PANEL_PALETTE.label],
|
|
158
|
+
[String(selectedBlock.content.length), DEFAULT_PANEL_PALETTE.dim],
|
|
159
|
+
]),
|
|
160
|
+
]
|
|
161
|
+
: [buildPanelLine(width, [[' No block selected', DEFAULT_PANEL_PALETTE.dim]])],
|
|
166
162
|
};
|
|
167
163
|
|
|
168
164
|
const reasoningSection = resolveScrollablePanelSection(width, height, {
|
|
@@ -195,7 +191,7 @@ export class ThinkingPanel extends BasePanel {
|
|
|
195
191
|
}
|
|
196
192
|
|
|
197
193
|
private _renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
|
|
198
|
-
const bg = isCursor ? C.
|
|
194
|
+
const bg = isCursor ? C.selectBg : '';
|
|
199
195
|
if (row.kind === 'header') {
|
|
200
196
|
const indicator = this.blocks[row.blockIndex]?.collapsed ? '▸' : '▾';
|
|
201
197
|
const active = this.blocks[row.blockIndex]?.active;
|
|
@@ -217,7 +213,7 @@ export class ThinkingPanel extends BasePanel {
|
|
|
217
213
|
const turnLabel = `Turn ${block.turnId}${block.active ? ' (streaming)' : ''}`;
|
|
218
214
|
rows.push({ kind: 'header', blockIndex: i, text: turnLabel });
|
|
219
215
|
if (!block.collapsed) {
|
|
220
|
-
const wrapped =
|
|
216
|
+
const wrapped = wrapText(block.content || '(empty)', Math.max(1, width - 2));
|
|
221
217
|
for (const line of wrapped) {
|
|
222
218
|
rows.push({ kind: 'content', text: line });
|
|
223
219
|
}
|