@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,4 +1,5 @@
|
|
|
1
1
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
+
import { formatLatencyMs } from '../utils/format-duration.ts';
|
|
2
3
|
import { BasePanel } from './base-panel.ts';
|
|
3
4
|
import { createEmptyLine, createStyledCell, type Line } from '../types/grid.ts';
|
|
4
5
|
import type { ProviderAuthRouteDescriptor } from '@pellux/goodvibes-sdk/platform/providers';
|
|
@@ -26,16 +27,17 @@ import type {
|
|
|
26
27
|
} from '../runtime/ui-read-models.ts';
|
|
27
28
|
import { evaluateSessionMaintenance } from '@/runtime/index.ts';
|
|
28
29
|
import {
|
|
30
|
+
buildAlignedRow,
|
|
29
31
|
buildBodyText,
|
|
30
32
|
buildDetailBlock,
|
|
31
33
|
buildEmptyState,
|
|
32
34
|
buildGuidanceLine,
|
|
35
|
+
buildKeyboardHints,
|
|
33
36
|
buildKeyValueLine,
|
|
34
|
-
buildPanelListRow,
|
|
35
|
-
buildPanelLine,
|
|
36
37
|
buildPanelWorkspace,
|
|
37
38
|
buildSummaryBlock,
|
|
38
39
|
DEFAULT_PANEL_PALETTE,
|
|
40
|
+
extendPalette,
|
|
39
41
|
resolvePrimaryScrollableSection,
|
|
40
42
|
type PanelWorkspaceSection,
|
|
41
43
|
} from './polish.ts';
|
|
@@ -62,22 +64,11 @@ export interface ProviderHealthPanelDeps {
|
|
|
62
64
|
// Colors
|
|
63
65
|
// ---------------------------------------------------------------------------
|
|
64
66
|
|
|
65
|
-
const C = {
|
|
66
|
-
title:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
unknown: '244',
|
|
71
|
-
label: '244',
|
|
72
|
-
value: '252',
|
|
73
|
-
dim: '240',
|
|
74
|
-
provName: '#e2e8f0',
|
|
75
|
-
errMsg: '#ff5f5f',
|
|
76
|
-
latGood: '#5fd700',
|
|
77
|
-
latWarn: '#ffaf00',
|
|
78
|
-
latBad: '#ff5f5f',
|
|
79
|
-
separator: '#374151',
|
|
80
|
-
} as const;
|
|
67
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
68
|
+
title: '#00ffff',
|
|
69
|
+
unknown: '244',
|
|
70
|
+
rowSelectBg: '#111827',
|
|
71
|
+
});
|
|
81
72
|
|
|
82
73
|
const LATENCY_WARN_MS = 2_000;
|
|
83
74
|
const LATENCY_BAD_MS = 5_000;
|
|
@@ -88,11 +79,11 @@ const LATENCY_BAD_MS = 5_000;
|
|
|
88
79
|
|
|
89
80
|
function statusDot(status: ProviderStatus): { char: string; color: string } {
|
|
90
81
|
switch (status) {
|
|
91
|
-
case 'healthy': return { char: '●', color: C.
|
|
92
|
-
case 'degraded': return { char: '◑', color: C.
|
|
93
|
-
case 'rate_limited': return { char: '◐', color: C.
|
|
94
|
-
case 'auth_error': return { char: '✕', color: C.
|
|
95
|
-
case 'unavailable': return { char: '✕', color: C.
|
|
82
|
+
case 'healthy': return { char: '●', color: C.good };
|
|
83
|
+
case 'degraded': return { char: '◑', color: C.warn };
|
|
84
|
+
case 'rate_limited': return { char: '◐', color: C.warn };
|
|
85
|
+
case 'auth_error': return { char: '✕', color: C.bad };
|
|
86
|
+
case 'unavailable': return { char: '✕', color: C.bad };
|
|
96
87
|
default: return { char: '○', color: C.unknown };
|
|
97
88
|
}
|
|
98
89
|
}
|
|
@@ -109,16 +100,13 @@ function statusLabel(status: ProviderStatus): string {
|
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
function latencyColor(ms: number): string {
|
|
112
|
-
if (ms >= LATENCY_BAD_MS) return C.
|
|
113
|
-
if (ms >= LATENCY_WARN_MS) return C.
|
|
114
|
-
return C.
|
|
103
|
+
if (ms >= LATENCY_BAD_MS) return C.bad;
|
|
104
|
+
if (ms >= LATENCY_WARN_MS) return C.warn;
|
|
105
|
+
return C.good;
|
|
115
106
|
}
|
|
116
107
|
|
|
117
108
|
function fmtMs(ms: number): string {
|
|
118
|
-
|
|
119
|
-
if (ms >= 10_000) return `${(ms / 1000).toFixed(1)}s`;
|
|
120
|
-
if (ms >= 1_000) return `${(ms / 1000).toFixed(2)}s`;
|
|
121
|
-
return `${Math.round(ms)}ms`;
|
|
109
|
+
return formatLatencyMs(ms);
|
|
122
110
|
}
|
|
123
111
|
|
|
124
112
|
function fmtAgo(ts: number | undefined): string {
|
|
@@ -154,11 +142,11 @@ interface ProviderRuntimeRecord {
|
|
|
154
142
|
function domainColor(level: HealthDomainSummary['level']): string {
|
|
155
143
|
switch (level) {
|
|
156
144
|
case 'good':
|
|
157
|
-
return C.
|
|
145
|
+
return C.good;
|
|
158
146
|
case 'warn':
|
|
159
|
-
return C.
|
|
147
|
+
return C.warn;
|
|
160
148
|
case 'bad':
|
|
161
|
-
return C.
|
|
149
|
+
return C.bad;
|
|
162
150
|
default:
|
|
163
151
|
return C.value;
|
|
164
152
|
}
|
|
@@ -184,9 +172,9 @@ function routeColor(route: ProviderPanelAuthRoute): string {
|
|
|
184
172
|
case 'subscription-oauth':
|
|
185
173
|
return C.title;
|
|
186
174
|
case 'service-oauth':
|
|
187
|
-
return C.
|
|
175
|
+
return C.good;
|
|
188
176
|
case 'api-key':
|
|
189
|
-
return C.
|
|
177
|
+
return C.warn;
|
|
190
178
|
case 'secret-ref':
|
|
191
179
|
return C.value;
|
|
192
180
|
case 'anonymous':
|
|
@@ -200,12 +188,12 @@ function routeColor(route: ProviderPanelAuthRoute): string {
|
|
|
200
188
|
function freshnessColor(freshness: ProviderPanelAuthFreshness): string {
|
|
201
189
|
switch (freshness) {
|
|
202
190
|
case 'expired':
|
|
203
|
-
return C.
|
|
191
|
+
return C.bad;
|
|
204
192
|
case 'expiring':
|
|
205
193
|
case 'pending':
|
|
206
|
-
return C.
|
|
194
|
+
return C.warn;
|
|
207
195
|
case 'healthy':
|
|
208
|
-
return C.
|
|
196
|
+
return C.good;
|
|
209
197
|
default:
|
|
210
198
|
return C.dim;
|
|
211
199
|
}
|
|
@@ -552,10 +540,10 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
552
540
|
{ command: '/provider', summary: 'review current provider and model selection' },
|
|
553
541
|
{ command: '/subscription', summary: 'review provider login and subscription state' },
|
|
554
542
|
],
|
|
555
|
-
{ ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
543
|
+
{ ...DEFAULT_PANEL_PALETTE, header: C.title },
|
|
556
544
|
),
|
|
557
545
|
}],
|
|
558
|
-
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
546
|
+
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title },
|
|
559
547
|
});
|
|
560
548
|
}
|
|
561
549
|
|
|
@@ -581,14 +569,14 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
581
569
|
const postureLines = [
|
|
582
570
|
buildKeyValueLine(width, [
|
|
583
571
|
{ label: 'providers', value: String(providers.length), valueColor: C.value },
|
|
584
|
-
{ label: 'online', value: String(online), valueColor: C.
|
|
585
|
-
{ label: 'rate-limited', value: String(rateLimited), valueColor: C.
|
|
586
|
-
{ label: 'error', value: String(errored), valueColor: C.
|
|
587
|
-
{ label: 'auth alerts', value: String(expiringAuth), valueColor: expiringAuth > 0 ? C.
|
|
588
|
-
{ label: 'account issues', value: String(accountIssues), valueColor: accountIssues > 0 ? C.
|
|
589
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
590
|
-
buildGuidanceLine(width, '/provider', 'review provider selection and routing if health posture degrades', { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
591
|
-
buildGuidanceLine(width, '/accounts', 'inspect auth routes, fallback posture, and billing-path safety', { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
572
|
+
{ label: 'online', value: String(online), valueColor: C.good },
|
|
573
|
+
{ label: 'rate-limited', value: String(rateLimited), valueColor: C.warn },
|
|
574
|
+
{ label: 'error', value: String(errored), valueColor: C.bad },
|
|
575
|
+
{ label: 'auth alerts', value: String(expiringAuth), valueColor: expiringAuth > 0 ? C.warn : C.dim },
|
|
576
|
+
{ label: 'account issues', value: String(accountIssues), valueColor: accountIssues > 0 ? C.bad : C.dim },
|
|
577
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }),
|
|
578
|
+
buildGuidanceLine(width, '/provider', 'review provider selection and routing if health posture degrades', { ...DEFAULT_PANEL_PALETTE, header: C.title }),
|
|
579
|
+
buildGuidanceLine(width, '/accounts', 'inspect auth routes, fallback posture, and billing-path safety', { ...DEFAULT_PANEL_PALETTE, header: C.title }),
|
|
592
580
|
];
|
|
593
581
|
|
|
594
582
|
const domainLines: Line[] = [];
|
|
@@ -603,18 +591,24 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
603
591
|
worktrees: this.deps.worktrees.getSnapshot(),
|
|
604
592
|
session: this.deps.session.getSnapshot(),
|
|
605
593
|
})) {
|
|
606
|
-
domainLines.push(
|
|
607
|
-
|
|
608
|
-
[
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
594
|
+
domainLines.push(buildAlignedRow(
|
|
595
|
+
width,
|
|
596
|
+
[
|
|
597
|
+
{ text: domain.name, fg: C.value, bold: true },
|
|
598
|
+
{ text: domain.summary, fg: domainColor(domain.level) },
|
|
599
|
+
{ text: domain.next, fg: C.dim },
|
|
600
|
+
],
|
|
601
|
+
[
|
|
602
|
+
{ width: 14 },
|
|
603
|
+
{ width: Math.max(10, width - 38) },
|
|
604
|
+
{ width: 20 },
|
|
605
|
+
],
|
|
606
|
+
));
|
|
613
607
|
for (const detail of domain.details.slice(0, 2)) {
|
|
614
|
-
domainLines.push(...buildBodyText(width, ` ${detail}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
608
|
+
domainLines.push(...buildBodyText(width, ` ${detail}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.dim));
|
|
615
609
|
}
|
|
616
610
|
if (domain.nextSteps.length > 1) {
|
|
617
|
-
domainLines.push(...buildBodyText(width, ` next: ${domain.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
611
|
+
domainLines.push(...buildBodyText(width, ` next: ${domain.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.title));
|
|
618
612
|
}
|
|
619
613
|
}
|
|
620
614
|
|
|
@@ -632,77 +626,114 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
632
626
|
session: session.session,
|
|
633
627
|
});
|
|
634
628
|
maintenanceLines.push(buildKeyValueLine(width, [
|
|
635
|
-
{ label: 'level', value: maintenance.level, valueColor: maintenance.level === 'needs-repair' ? C.
|
|
629
|
+
{ label: 'level', value: maintenance.level, valueColor: maintenance.level === 'needs-repair' ? C.bad : maintenance.level === 'suggest-compact' || maintenance.level === 'watch' ? C.warn : C.good },
|
|
636
630
|
{ label: 'guidance', value: maintenance.guidanceMode, valueColor: C.value },
|
|
637
|
-
{ label: 'usage', value: `${maintenance.usagePct}%`, valueColor: maintenance.usagePct >= 80 ? C.
|
|
631
|
+
{ label: 'usage', value: `${maintenance.usagePct}%`, valueColor: maintenance.usagePct >= 80 ? C.bad : maintenance.usagePct >= 70 ? C.warn : C.value },
|
|
638
632
|
{ label: 'remaining', value: maintenance.remainingTokens.toLocaleString(), valueColor: C.value },
|
|
639
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
633
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }));
|
|
640
634
|
for (const reason of maintenance.reasons.slice(0, 3)) {
|
|
641
|
-
maintenanceLines.push(...buildBodyText(width, reason, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
635
|
+
maintenanceLines.push(...buildBodyText(width, reason, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.dim));
|
|
642
636
|
}
|
|
643
637
|
if (maintenance.nextSteps.length > 0) {
|
|
644
|
-
maintenanceLines.push(...buildBodyText(width, `Next: ${maintenance.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
638
|
+
maintenanceLines.push(...buildBodyText(width, `Next: ${maintenance.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.title));
|
|
645
639
|
}
|
|
646
640
|
if (selectedName) {
|
|
647
641
|
const status = selectedHealth?.status ?? 'unknown';
|
|
648
642
|
selectedLines.push(buildKeyValueLine(width, [
|
|
649
|
-
{ label: 'provider', value: selectedName, valueColor: C.
|
|
643
|
+
{ label: 'provider', value: selectedName, valueColor: C.value },
|
|
650
644
|
{ label: 'status', value: statusLabel(status), valueColor: statusDot(status).color },
|
|
651
645
|
{ label: 'last ok', value: fmtAgo(selectedHealth?.lastSuccessAt), valueColor: C.value },
|
|
652
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
646
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }));
|
|
653
647
|
if (selectedHealth?.rateLimitExpiresAt && selectedHealth.rateLimitExpiresAt > Date.now()) {
|
|
654
|
-
selectedLines.push(...buildBodyText(width, `Cooldown: ${fmtCooldown(selectedHealth.rateLimitExpiresAt)}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
648
|
+
selectedLines.push(...buildBodyText(width, `Cooldown: ${fmtCooldown(selectedHealth.rateLimitExpiresAt)}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.warn));
|
|
655
649
|
}
|
|
656
650
|
if (selectedHealth?.lastErrorMessage) {
|
|
657
|
-
selectedLines.push(...buildBodyText(width, `Last error: ${selectedHealth.lastErrorMessage}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
651
|
+
selectedLines.push(...buildBodyText(width, `Last error: ${selectedHealth.lastErrorMessage}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.bad));
|
|
658
652
|
}
|
|
659
653
|
if (selectedAccount) {
|
|
660
654
|
selectedLines.push(buildKeyValueLine(width, [
|
|
661
655
|
{ label: 'route', value: selectedAccount.activeRoute, valueColor: routeColor(selectedAccount.activeRoute) },
|
|
662
656
|
{ label: 'preferred', value: selectedAccount.preferredRoute, valueColor: C.dim },
|
|
663
657
|
{ label: 'freshness', value: selectedAccount.authFreshness, valueColor: freshnessColor(selectedAccount.authFreshness) },
|
|
664
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
658
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }));
|
|
665
659
|
selectedLines.push(buildKeyValueLine(width, [
|
|
666
660
|
{ label: 'models', value: String(selectedAccount.modelCount), valueColor: C.value },
|
|
667
|
-
{ label: 'active', value: selectedAccount.active ? 'yes' : 'no', valueColor: selectedAccount.active ? C.
|
|
668
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
669
|
-
selectedLines.push(...buildBodyText(width, `Auth route: ${selectedAccount.activeRouteReason}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
661
|
+
{ label: 'active', value: selectedAccount.active ? 'yes' : 'no', valueColor: selectedAccount.active ? C.good : C.dim },
|
|
662
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }));
|
|
663
|
+
selectedLines.push(...buildBodyText(width, `Auth route: ${selectedAccount.activeRouteReason}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.dim));
|
|
670
664
|
if (selectedAccount.fallbackRisk) {
|
|
671
|
-
selectedLines.push(...buildBodyText(width, `Fallback: ${selectedAccount.fallbackRisk}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
665
|
+
selectedLines.push(...buildBodyText(width, `Fallback: ${selectedAccount.fallbackRisk}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.warn));
|
|
672
666
|
}
|
|
673
667
|
if (selectedAccount.issues.length > 0) {
|
|
674
|
-
selectedLines.push(...buildBodyText(width, `Issue: ${selectedAccount.issues[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
668
|
+
selectedLines.push(...buildBodyText(width, `Issue: ${selectedAccount.issues[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.bad));
|
|
675
669
|
}
|
|
676
670
|
if (selectedAccount.recommendedActions.length > 0) {
|
|
677
|
-
selectedLines.push(...buildBodyText(width, `Next: ${selectedAccount.recommendedActions[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
671
|
+
selectedLines.push(...buildBodyText(width, `Next: ${selectedAccount.recommendedActions[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.title));
|
|
678
672
|
}
|
|
679
673
|
}
|
|
680
674
|
}
|
|
681
675
|
|
|
682
|
-
const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Health posture', postureLines, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
676
|
+
const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Health posture', postureLines, { ...DEFAULT_PANEL_PALETTE, header: C.title }) };
|
|
683
677
|
const domainsSection: PanelWorkspaceSection = { title: 'Repair Domains', lines: domainLines };
|
|
684
678
|
const maintenanceSections = maintenanceLines.length > 0 ? [{ title: 'Session Maintenance', lines: maintenanceLines } satisfies PanelWorkspaceSection] : [];
|
|
685
|
-
const selectedSections = selectedLines.length > 0 ? [{ lines: buildDetailBlock(width, 'Selected provider', selectedLines, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
679
|
+
const selectedSections = selectedLines.length > 0 ? [{ lines: buildDetailBlock(width, 'Selected provider', selectedLines, { ...DEFAULT_PANEL_PALETTE, header: C.title }) } satisfies PanelWorkspaceSection] : [];
|
|
680
|
+
const footerHint = buildKeyboardHints(width, [
|
|
681
|
+
{ keys: 'j/k or Up/Down', label: 'select provider' },
|
|
682
|
+
{ keys: '/provider', label: 'switch model' },
|
|
683
|
+
{ keys: '/accounts', label: 'auth routes' },
|
|
684
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title });
|
|
685
|
+
const providerColumnHeader = buildAlignedRow(
|
|
686
|
+
width,
|
|
687
|
+
[
|
|
688
|
+
{ text: 'provider', fg: C.label, bold: true },
|
|
689
|
+
{ text: 'status', fg: C.label, bold: true },
|
|
690
|
+
{ text: 'lat', fg: C.label, bold: true },
|
|
691
|
+
{ text: 'last ok', fg: C.label, bold: true },
|
|
692
|
+
{ text: 'auth', fg: C.label, bold: true },
|
|
693
|
+
],
|
|
694
|
+
[
|
|
695
|
+
{ width: 18 },
|
|
696
|
+
{ width: 13 },
|
|
697
|
+
{ width: 8, align: 'right' },
|
|
698
|
+
{ width: 10, align: 'right' },
|
|
699
|
+
{ width: 12 },
|
|
700
|
+
],
|
|
701
|
+
{ marker: '▸' },
|
|
702
|
+
);
|
|
686
703
|
const resolvedProvidersSection = resolvePrimaryScrollableSection(width, height, {
|
|
687
704
|
intro,
|
|
688
|
-
footerLines: [
|
|
689
|
-
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
705
|
+
footerLines: [footerHint],
|
|
706
|
+
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title },
|
|
690
707
|
beforeSections: [postureSection, domainsSection, ...maintenanceSections],
|
|
691
708
|
section: {
|
|
692
709
|
title: 'Providers',
|
|
710
|
+
fixedLines: [providerColumnHeader],
|
|
693
711
|
scrollableLines: providers.map((name, absolute) => {
|
|
694
712
|
const health = this.providerHealthTracker.get(name);
|
|
695
713
|
const status = health?.status ?? 'unknown';
|
|
714
|
+
const dot = statusDot(status);
|
|
696
715
|
const latency = health?.lastLatencyMs !== undefined ? fmtMs(health.lastLatencyMs) : 'n/a';
|
|
697
716
|
const latencyFg = health?.lastLatencyMs !== undefined ? latencyColor(health.lastLatencyMs) : C.dim;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
717
|
+
const account = this._accountRecords.get(name);
|
|
718
|
+
const authFg = account ? freshnessColor(account.authFreshness) : C.dim;
|
|
719
|
+
return buildAlignedRow(
|
|
720
|
+
width,
|
|
721
|
+
[
|
|
722
|
+
{ text: `${dot.char} ${name}`, fg: C.value, bold: absolute === this._selectedIndex },
|
|
723
|
+
{ text: statusLabel(status), fg: dot.color },
|
|
724
|
+
{ text: latency, fg: latencyFg },
|
|
725
|
+
{ text: fmtAgo(health?.lastSuccessAt), fg: C.value },
|
|
726
|
+
{ text: account ? account.authFreshness : '-', fg: authFg },
|
|
727
|
+
],
|
|
728
|
+
[
|
|
729
|
+
{ width: 18 },
|
|
730
|
+
{ width: 13 },
|
|
731
|
+
{ width: 8, align: 'right' },
|
|
732
|
+
{ width: 10, align: 'right' },
|
|
733
|
+
{ width: 12 },
|
|
734
|
+
],
|
|
735
|
+
{ selected: absolute === this._selectedIndex, selectedBg: C.rowSelectBg, marker: '▸' },
|
|
736
|
+
);
|
|
706
737
|
}),
|
|
707
738
|
selectedIndex: this._selectedIndex,
|
|
708
739
|
scrollOffset: this._scrollOffset,
|
|
@@ -724,8 +755,8 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
724
755
|
title: 'Health',
|
|
725
756
|
intro,
|
|
726
757
|
sections,
|
|
727
|
-
footerLines: [
|
|
728
|
-
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
758
|
+
footerLines: [footerHint],
|
|
759
|
+
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title },
|
|
729
760
|
});
|
|
730
761
|
}
|
|
731
762
|
}
|
|
@@ -5,18 +5,31 @@ import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
|
5
5
|
import type { UiProvidersSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
6
6
|
import {
|
|
7
7
|
buildEmptyState,
|
|
8
|
+
buildKeyboardHints,
|
|
8
9
|
buildKeyValueLine,
|
|
10
|
+
buildPanelLine,
|
|
9
11
|
buildStyledPanelLine,
|
|
10
12
|
buildPanelWorkspace,
|
|
11
13
|
DEFAULT_PANEL_PALETTE,
|
|
14
|
+
extendPalette,
|
|
12
15
|
type PanelWorkspaceSection,
|
|
13
16
|
} from './polish.ts';
|
|
14
17
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
18
|
+
import { formatLatencyMs } from '../utils/format-duration.ts';
|
|
15
19
|
|
|
16
20
|
// ---------------------------------------------------------------------------
|
|
17
21
|
// Constants
|
|
18
22
|
// ---------------------------------------------------------------------------
|
|
19
23
|
|
|
24
|
+
// Domain-specific tones not covered by the base palette (latency/token greys).
|
|
25
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
26
|
+
muted2: '#6b7280', // secondary dim grey for unit labels
|
|
27
|
+
sep: '#374151', // inline separator grey
|
|
28
|
+
tokenDim: '#64748b', // token-count grey
|
|
29
|
+
p95: '#a78bfa', // p95 latency accent
|
|
30
|
+
latYellow: '#eab308', // mid-latency warning
|
|
31
|
+
});
|
|
32
|
+
|
|
20
33
|
const SPARKLINE_CHARS = '._-:=+*#';
|
|
21
34
|
const LATENCY_RING_SIZE = 20;
|
|
22
35
|
|
|
@@ -202,7 +215,10 @@ export class ProviderStatsPanel extends BasePanel {
|
|
|
202
215
|
width,
|
|
203
216
|
' No providers registered',
|
|
204
217
|
'Load or configure a provider to begin collecting per-provider latency and error metrics.',
|
|
205
|
-
[
|
|
218
|
+
[
|
|
219
|
+
{ command: '/provider', summary: 'review current provider and model selection' },
|
|
220
|
+
{ command: '/subscription', summary: 'sign in to a subscription-backed provider' },
|
|
221
|
+
],
|
|
206
222
|
DEFAULT_PANEL_PALETTE,
|
|
207
223
|
),
|
|
208
224
|
},
|
|
@@ -245,6 +261,18 @@ export class ProviderStatsPanel extends BasePanel {
|
|
|
245
261
|
title: ' Provider Stats',
|
|
246
262
|
intro: 'Per-provider request performance, latency distribution, error pressure, and session totals.',
|
|
247
263
|
sections: providerSections,
|
|
264
|
+
footerLines: [
|
|
265
|
+
buildPanelLine(width, [
|
|
266
|
+
[' sparkline ', C.muted2],
|
|
267
|
+
['low', C.good],
|
|
268
|
+
[' → ', C.muted2],
|
|
269
|
+
['high latency', C.bad],
|
|
270
|
+
]),
|
|
271
|
+
buildKeyboardHints(width, [
|
|
272
|
+
{ keys: '/provider', label: 'switch provider/model' },
|
|
273
|
+
{ keys: '/health', label: 'live provider health' },
|
|
274
|
+
], DEFAULT_PANEL_PALETTE),
|
|
275
|
+
],
|
|
248
276
|
palette: DEFAULT_PANEL_PALETTE,
|
|
249
277
|
});
|
|
250
278
|
}
|
|
@@ -262,7 +290,7 @@ export class ProviderStatsPanel extends BasePanel {
|
|
|
262
290
|
|
|
263
291
|
// Determine health
|
|
264
292
|
const hasErrors = rec !== undefined && rec.errors > 0;
|
|
265
|
-
const dotColor = hasErrors ?
|
|
293
|
+
const dotColor = hasErrors ? C.bad : C.good;
|
|
266
294
|
|
|
267
295
|
// Model ID (truncated)
|
|
268
296
|
const modelId = rec?.lastModelId ?? 'n/a';
|
|
@@ -271,18 +299,18 @@ export class ProviderStatsPanel extends BasePanel {
|
|
|
271
299
|
// Header row: * provider model
|
|
272
300
|
// Build as segments to avoid multi-byte char indexing issues
|
|
273
301
|
const headerLine = buildStyledPanelLine(width, [
|
|
274
|
-
{ text: ' ', fg:
|
|
302
|
+
{ text: ' ', fg: C.label },
|
|
275
303
|
{ text: '●', fg: dotColor },
|
|
276
|
-
{ text: ' ', fg:
|
|
277
|
-
{ text: `${truncateDisplay(provName, 14).padEnd(14)} `, fg:
|
|
278
|
-
{ text: modelDisplay, fg:
|
|
304
|
+
{ text: ' ', fg: C.label },
|
|
305
|
+
{ text: `${truncateDisplay(provName, 14).padEnd(14)} `, fg: C.value, bold: true },
|
|
306
|
+
{ text: modelDisplay, fg: C.accent },
|
|
279
307
|
]);
|
|
280
308
|
|
|
281
309
|
rows.push(headerLine);
|
|
282
310
|
|
|
283
311
|
if (rec === undefined || rec.requests === 0) {
|
|
284
312
|
rows.push(buildStyledPanelLine(width, [
|
|
285
|
-
{ text: ' No requests yet.', fg:
|
|
313
|
+
{ text: ' No requests yet.', fg: C.muted2 },
|
|
286
314
|
]));
|
|
287
315
|
} else {
|
|
288
316
|
const avgLatency = this._avg(rec.latencies);
|
|
@@ -291,24 +319,24 @@ export class ProviderStatsPanel extends BasePanel {
|
|
|
291
319
|
const sparkline = this._sparkline(rec.latencies);
|
|
292
320
|
|
|
293
321
|
const latFg = avgLatency < LATENCY_GREEN
|
|
294
|
-
?
|
|
322
|
+
? C.good
|
|
295
323
|
: avgLatency < LATENCY_YELLOW
|
|
296
|
-
?
|
|
297
|
-
:
|
|
324
|
+
? C.latYellow
|
|
325
|
+
: C.bad;
|
|
298
326
|
|
|
299
327
|
const segments = [
|
|
300
|
-
{ text: ' avg ', fg:
|
|
328
|
+
{ text: ' avg ', fg: C.muted2 },
|
|
301
329
|
{ text: this._fmtMs(avgLatency).padStart(6), fg: latFg, bold: true },
|
|
302
|
-
{ text: ' p95 ', fg:
|
|
303
|
-
{ text: this._fmtMs(p95Latency).padStart(6), fg:
|
|
304
|
-
{ text: ' ', fg:
|
|
330
|
+
{ text: ' p95 ', fg: C.muted2 },
|
|
331
|
+
{ text: this._fmtMs(p95Latency).padStart(6), fg: C.p95 },
|
|
332
|
+
{ text: ' ', fg: C.sep },
|
|
305
333
|
{ text: sparkline, fg: latFg },
|
|
306
|
-
{ text: ' err ', fg:
|
|
307
|
-
{ text: `${errRate.toFixed(0).padStart(3)}%`, fg: errRate > 0 ?
|
|
308
|
-
{ text: ` ${rec.requests.toString().padStart(4)}r`, fg:
|
|
334
|
+
{ text: ' err ', fg: C.muted2 },
|
|
335
|
+
{ text: `${errRate.toFixed(0).padStart(3)}%`, fg: errRate > 0 ? C.bad : C.good },
|
|
336
|
+
{ text: ` ${rec.requests.toString().padStart(4)}r`, fg: C.label },
|
|
309
337
|
] as const;
|
|
310
338
|
const tokenSegment = rec.totalTokens > 0
|
|
311
|
-
? [{ text: ` ${rec.totalTokens.toString().padStart(6)}tok`, fg:
|
|
339
|
+
? [{ text: ` ${rec.totalTokens.toString().padStart(6)}tok`, fg: C.tokenDim }]
|
|
312
340
|
: [];
|
|
313
341
|
rows.push(buildStyledPanelLine(width, [...segments, ...tokenSegment]));
|
|
314
342
|
}
|
|
@@ -351,10 +379,7 @@ export class ProviderStatsPanel extends BasePanel {
|
|
|
351
379
|
}
|
|
352
380
|
|
|
353
381
|
private _fmtMs(ms: number): string {
|
|
354
|
-
|
|
355
|
-
if (ms >= 10000) return `${(ms / 1000).toFixed(1)}s`;
|
|
356
|
-
if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s`;
|
|
357
|
-
return `${Math.round(ms)}ms`;
|
|
382
|
+
return formatLatencyMs(ms);
|
|
358
383
|
}
|
|
359
384
|
|
|
360
385
|
private _latencyColor(ms: number): string {
|
package/src/panels/qr-panel.ts
CHANGED
|
@@ -2,9 +2,12 @@ import type { Line } from '../types/grid.ts';
|
|
|
2
2
|
import { createEmptyLine } from '../types/grid.ts';
|
|
3
3
|
import { BasePanel } from './base-panel.ts';
|
|
4
4
|
import {
|
|
5
|
+
buildKeyboardHints,
|
|
5
6
|
buildPanelLine,
|
|
7
|
+
buildPanelTitle,
|
|
6
8
|
DEFAULT_PANEL_PALETTE,
|
|
7
9
|
} from './polish.ts';
|
|
10
|
+
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
8
11
|
import { renderQrMatrix, generateQrMatrix } from '../renderer/qr-renderer.ts';
|
|
9
12
|
import { encodeConnectionPayload } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
10
13
|
|
|
@@ -101,32 +104,41 @@ export class QrPanel extends BasePanel {
|
|
|
101
104
|
const lines: Line[] = [];
|
|
102
105
|
|
|
103
106
|
const { url, token, username, password } = this.connectionInfo;
|
|
107
|
+
const valueWidth = Math.max(0, width - 12);
|
|
108
|
+
|
|
109
|
+
// ── Title + purpose: tell the operator exactly what this code is for ────
|
|
110
|
+
lines.push(buildPanelTitle(width, 'Companion Pairing', C));
|
|
111
|
+
lines.push(
|
|
112
|
+
buildPanelLine(width, [
|
|
113
|
+
[' Scan with the GoodVibes companion app to pair this session.', C.hint],
|
|
114
|
+
]),
|
|
115
|
+
);
|
|
104
116
|
|
|
105
117
|
// ── Connection info header ─────────────────────────────────────────────
|
|
106
118
|
lines.push(createEmptyLine(width));
|
|
107
119
|
lines.push(
|
|
108
120
|
buildPanelLine(width, [
|
|
109
121
|
[' URL ', C.label],
|
|
110
|
-
[url
|
|
122
|
+
[truncateDisplay(url, valueWidth), C.url],
|
|
111
123
|
]),
|
|
112
124
|
);
|
|
113
125
|
lines.push(
|
|
114
126
|
buildPanelLine(width, [
|
|
115
127
|
[' Token ', C.label],
|
|
116
|
-
[token, C.token],
|
|
128
|
+
[truncateDisplay(token, valueWidth), C.token],
|
|
117
129
|
]),
|
|
118
130
|
);
|
|
119
131
|
lines.push(
|
|
120
132
|
buildPanelLine(width, [
|
|
121
133
|
[' Username ', C.label],
|
|
122
|
-
[username
|
|
134
|
+
[truncateDisplay(username, valueWidth), C.value],
|
|
123
135
|
]),
|
|
124
136
|
);
|
|
125
137
|
if (password !== undefined) {
|
|
126
138
|
lines.push(
|
|
127
139
|
buildPanelLine(width, [
|
|
128
140
|
[' Password ', C.label],
|
|
129
|
-
[password, C.value],
|
|
141
|
+
[truncateDisplay(password, valueWidth), C.value],
|
|
130
142
|
]),
|
|
131
143
|
);
|
|
132
144
|
}
|
|
@@ -158,13 +170,13 @@ export class QrPanel extends BasePanel {
|
|
|
158
170
|
);
|
|
159
171
|
}
|
|
160
172
|
|
|
161
|
-
// ── Hints
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
// ── Hints ── only advertise actions that are actually wired ─────────────
|
|
174
|
+
const hints: Array<{ keys: string; label: string }> = [];
|
|
175
|
+
if (this.regenerateToken) hints.push({ keys: 'r', label: 'regenerate token' });
|
|
176
|
+
if (this.copyToClipboard) hints.push({ keys: 'c', label: 'copy token' });
|
|
177
|
+
const hintsLine = hints.length > 0
|
|
178
|
+
? buildKeyboardHints(width, hints, C)
|
|
179
|
+
: buildPanelLine(width, [[' Pairing is read-only in this surface.', C.dim]]);
|
|
168
180
|
|
|
169
181
|
// Push hints at the bottom if we have room, otherwise append after QR
|
|
170
182
|
const remaining = height - lines.length;
|
|
@@ -200,7 +200,7 @@ export class RemotePanel extends BasePanel {
|
|
|
200
200
|
if (daemon.lastError) {
|
|
201
201
|
postureLines.push(buildPanelLine(width, [
|
|
202
202
|
[' daemon error ', C.label],
|
|
203
|
-
[daemon.lastError
|
|
203
|
+
[truncateDisplay(daemon.lastError, Math.max(0, width - 14)), C.error],
|
|
204
204
|
]));
|
|
205
205
|
}
|
|
206
206
|
postureLines.push(
|
|
@@ -208,9 +208,16 @@ export class RemotePanel extends BasePanel {
|
|
|
208
208
|
buildGuidanceLine(width, '/remote capabilities', 'inspect transport support before routing remote work or reattaching a session', C),
|
|
209
209
|
);
|
|
210
210
|
|
|
211
|
+
const canBrowse = activeConnections.length > 0 || contracts.length > 0;
|
|
212
|
+
const canSwitch = contracts.length > 0 && activeConnections.length > 0;
|
|
213
|
+
const navHint = !canBrowse
|
|
214
|
+
? ` focus=${this.browseMode} idle - no connections or contracts to browse`
|
|
215
|
+
: canSwitch
|
|
216
|
+
? ` focus=${this.browseMode} Up/Down move Tab switch connections/contracts`
|
|
217
|
+
: ` focus=${this.browseMode} Up/Down move`;
|
|
211
218
|
const footerLines = [
|
|
212
219
|
buildGuidanceLine(width, '/remote setup', 'review bridge, tunnel, env, and bootstrap flows for self-hosted remote work', C),
|
|
213
|
-
buildPanelLine(width, [[
|
|
220
|
+
buildPanelLine(width, [[navHint, C.dim]]),
|
|
214
221
|
] as const;
|
|
215
222
|
|
|
216
223
|
if (activeConnections.length === 0 && contracts.length === 0) {
|
|
@@ -318,7 +325,7 @@ export class RemotePanel extends BasePanel {
|
|
|
318
325
|
[' Review: ', C.label],
|
|
319
326
|
[supervisorEntry.negotiation.reviewMode, supervisorEntry.negotiation.reviewMode === 'wrfc' ? C.ok : C.dim],
|
|
320
327
|
]));
|
|
321
|
-
detailRows.push(buildPanelLine(width, [[` ${supervisorEntry.heartbeat.detail}
|
|
328
|
+
detailRows.push(buildPanelLine(width, [[truncateDisplay(` ${supervisorEntry.heartbeat.detail}`, width), C.dim]]));
|
|
322
329
|
}
|
|
323
330
|
|
|
324
331
|
const recentArtifact = snapshot.artifacts.find((artifact) => artifact.runnerId === selected.agentId);
|
|
@@ -400,7 +407,7 @@ export class RemotePanel extends BasePanel {
|
|
|
400
407
|
{ text: ` ${connection.transportState.padEnd(18)}`, fg: stateColor(connection.transportState) },
|
|
401
408
|
{ text: ` msgs=${String(connection.messageCount).padEnd(6)}`, fg: C.info },
|
|
402
409
|
{ text: ` errs=${String(connection.errorCount).padEnd(4)}`, fg: connection.errorCount > 0 ? C.warn : C.dim },
|
|
403
|
-
{ text: ` ${connection.label}
|
|
410
|
+
{ text: truncateDisplay(` ${connection.label}`, Math.max(0, width - 56)), fg: C.dim },
|
|
404
411
|
], C, { selected: absolute === this.selectedIndex, selectedBg: C.headerBg });
|
|
405
412
|
})
|
|
406
413
|
: [
|
|
@@ -408,7 +415,7 @@ export class RemotePanel extends BasePanel {
|
|
|
408
415
|
return buildPanelListRow(width, [
|
|
409
416
|
{ text: contract.runnerId.padEnd(18), fg: C.value },
|
|
410
417
|
{ text: ` ${contract.transport.state.padEnd(18)}`, fg: stateColor(contract.transport.state) },
|
|
411
|
-
{ text: ` ${contract.template}
|
|
418
|
+
{ text: truncateDisplay(` ${contract.template}`, Math.max(0, width - 42)), fg: C.dim },
|
|
412
419
|
], C, { selected: absolute === this.selectedIndex, selectedBg: C.headerBg });
|
|
413
420
|
}),
|
|
414
421
|
buildPanelLine(width, [[' No active connection is currently attached to these contracts.', C.dim]]),
|