@pellux/goodvibes-tui 0.26.0 → 0.27.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 +17 -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/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +49 -2
- package/src/core/session-recovery.ts +24 -18
- package/src/core/system-message-router.ts +42 -26
- package/src/core/turn-event-wiring.ts +12 -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/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-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-ui-state.ts +1 -1
- package/src/input/tts-settings-actions.ts +2 -1
- package/src/main.ts +52 -59
- package/src/panels/agent-inspector-panel.ts +72 -2
- package/src/panels/agent-logs-panel.ts +127 -19
- package/src/panels/base-panel.ts +2 -1
- package/src/panels/builtin/agent.ts +3 -1
- package/src/panels/builtin/development.ts +1 -0
- package/src/panels/builtin/session.ts +1 -1
- package/src/panels/context-visualizer-panel.ts +24 -14
- package/src/panels/cost-tracker-panel.ts +7 -13
- package/src/panels/debug-panel.ts +11 -22
- package/src/panels/docs-panel.ts +14 -24
- package/src/panels/file-explorer-panel.ts +2 -1
- package/src/panels/file-preview-panel.ts +2 -1
- package/src/panels/git-panel.ts +10 -17
- package/src/panels/marketplace-panel.ts +2 -1
- package/src/panels/memory-panel.ts +2 -1
- package/src/panels/project-planning-panel.ts +5 -4
- package/src/panels/provider-health-panel.ts +56 -67
- package/src/panels/schedule-panel.ts +4 -7
- package/src/panels/session-browser-panel.ts +13 -21
- package/src/panels/skills-panel.ts +2 -1
- package/src/panels/tasks-panel.ts +2 -7
- package/src/panels/thinking-panel.ts +6 -11
- package/src/panels/token-budget-panel.ts +31 -15
- package/src/panels/tool-inspector-panel.ts +10 -18
- package/src/panels/work-plan-panel.ts +2 -1
- package/src/panels/wrfc-panel.ts +37 -35
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/modal-utils.ts +0 -10
- package/src/renderer/process-modal.ts +2 -2
- 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/wrfc-persistence.ts +20 -5
- package/src/shell/ui-openers.ts +3 -2
- package/src/verification/live-verifier.ts +4 -3
- package/src/version.ts +1 -1
- package/src/core/context-auto-compact.ts +0 -110
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
buildPanelWorkspace,
|
|
37
37
|
buildSummaryBlock,
|
|
38
38
|
DEFAULT_PANEL_PALETTE,
|
|
39
|
+
extendPalette,
|
|
39
40
|
resolvePrimaryScrollableSection,
|
|
40
41
|
type PanelWorkspaceSection,
|
|
41
42
|
} from './polish.ts';
|
|
@@ -62,22 +63,10 @@ export interface ProviderHealthPanelDeps {
|
|
|
62
63
|
// Colors
|
|
63
64
|
// ---------------------------------------------------------------------------
|
|
64
65
|
|
|
65
|
-
const C = {
|
|
66
|
-
title:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
error: '#ff5f5f',
|
|
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;
|
|
66
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
67
|
+
title: '#00ffff',
|
|
68
|
+
unknown: '244',
|
|
69
|
+
});
|
|
81
70
|
|
|
82
71
|
const LATENCY_WARN_MS = 2_000;
|
|
83
72
|
const LATENCY_BAD_MS = 5_000;
|
|
@@ -88,11 +77,11 @@ const LATENCY_BAD_MS = 5_000;
|
|
|
88
77
|
|
|
89
78
|
function statusDot(status: ProviderStatus): { char: string; color: string } {
|
|
90
79
|
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.
|
|
80
|
+
case 'healthy': return { char: '●', color: C.good };
|
|
81
|
+
case 'degraded': return { char: '◑', color: C.warn };
|
|
82
|
+
case 'rate_limited': return { char: '◐', color: C.warn };
|
|
83
|
+
case 'auth_error': return { char: '✕', color: C.bad };
|
|
84
|
+
case 'unavailable': return { char: '✕', color: C.bad };
|
|
96
85
|
default: return { char: '○', color: C.unknown };
|
|
97
86
|
}
|
|
98
87
|
}
|
|
@@ -109,9 +98,9 @@ function statusLabel(status: ProviderStatus): string {
|
|
|
109
98
|
}
|
|
110
99
|
|
|
111
100
|
function latencyColor(ms: number): string {
|
|
112
|
-
if (ms >= LATENCY_BAD_MS) return C.
|
|
113
|
-
if (ms >= LATENCY_WARN_MS) return C.
|
|
114
|
-
return C.
|
|
101
|
+
if (ms >= LATENCY_BAD_MS) return C.bad;
|
|
102
|
+
if (ms >= LATENCY_WARN_MS) return C.warn;
|
|
103
|
+
return C.good;
|
|
115
104
|
}
|
|
116
105
|
|
|
117
106
|
function fmtMs(ms: number): string {
|
|
@@ -154,11 +143,11 @@ interface ProviderRuntimeRecord {
|
|
|
154
143
|
function domainColor(level: HealthDomainSummary['level']): string {
|
|
155
144
|
switch (level) {
|
|
156
145
|
case 'good':
|
|
157
|
-
return C.
|
|
146
|
+
return C.good;
|
|
158
147
|
case 'warn':
|
|
159
|
-
return C.
|
|
148
|
+
return C.warn;
|
|
160
149
|
case 'bad':
|
|
161
|
-
return C.
|
|
150
|
+
return C.bad;
|
|
162
151
|
default:
|
|
163
152
|
return C.value;
|
|
164
153
|
}
|
|
@@ -184,9 +173,9 @@ function routeColor(route: ProviderPanelAuthRoute): string {
|
|
|
184
173
|
case 'subscription-oauth':
|
|
185
174
|
return C.title;
|
|
186
175
|
case 'service-oauth':
|
|
187
|
-
return C.
|
|
176
|
+
return C.good;
|
|
188
177
|
case 'api-key':
|
|
189
|
-
return C.
|
|
178
|
+
return C.warn;
|
|
190
179
|
case 'secret-ref':
|
|
191
180
|
return C.value;
|
|
192
181
|
case 'anonymous':
|
|
@@ -200,12 +189,12 @@ function routeColor(route: ProviderPanelAuthRoute): string {
|
|
|
200
189
|
function freshnessColor(freshness: ProviderPanelAuthFreshness): string {
|
|
201
190
|
switch (freshness) {
|
|
202
191
|
case 'expired':
|
|
203
|
-
return C.
|
|
192
|
+
return C.bad;
|
|
204
193
|
case 'expiring':
|
|
205
194
|
case 'pending':
|
|
206
|
-
return C.
|
|
195
|
+
return C.warn;
|
|
207
196
|
case 'healthy':
|
|
208
|
-
return C.
|
|
197
|
+
return C.good;
|
|
209
198
|
default:
|
|
210
199
|
return C.dim;
|
|
211
200
|
}
|
|
@@ -552,10 +541,10 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
552
541
|
{ command: '/provider', summary: 'review current provider and model selection' },
|
|
553
542
|
{ command: '/subscription', summary: 'review provider login and subscription state' },
|
|
554
543
|
],
|
|
555
|
-
{ ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
544
|
+
{ ...DEFAULT_PANEL_PALETTE, header: C.title },
|
|
556
545
|
),
|
|
557
546
|
}],
|
|
558
|
-
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
547
|
+
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title },
|
|
559
548
|
});
|
|
560
549
|
}
|
|
561
550
|
|
|
@@ -581,14 +570,14 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
581
570
|
const postureLines = [
|
|
582
571
|
buildKeyValueLine(width, [
|
|
583
572
|
{ 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
|
|
573
|
+
{ label: 'online', value: String(online), valueColor: C.good },
|
|
574
|
+
{ label: 'rate-limited', value: String(rateLimited), valueColor: C.warn },
|
|
575
|
+
{ label: 'error', value: String(errored), valueColor: C.bad },
|
|
576
|
+
{ label: 'auth alerts', value: String(expiringAuth), valueColor: expiringAuth > 0 ? C.warn : C.dim },
|
|
577
|
+
{ label: 'account issues', value: String(accountIssues), valueColor: accountIssues > 0 ? C.bad : C.dim },
|
|
578
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }),
|
|
579
|
+
buildGuidanceLine(width, '/provider', 'review provider selection and routing if health posture degrades', { ...DEFAULT_PANEL_PALETTE, header: C.title }),
|
|
580
|
+
buildGuidanceLine(width, '/accounts', 'inspect auth routes, fallback posture, and billing-path safety', { ...DEFAULT_PANEL_PALETTE, header: C.title }),
|
|
592
581
|
];
|
|
593
582
|
|
|
594
583
|
const domainLines: Line[] = [];
|
|
@@ -605,16 +594,16 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
605
594
|
})) {
|
|
606
595
|
domainLines.push(buildPanelLine(width, [
|
|
607
596
|
[' ', C.label],
|
|
608
|
-
[domain.name.padEnd(14), C.
|
|
597
|
+
[domain.name.padEnd(14), C.value],
|
|
609
598
|
[domain.summary.slice(0, Math.max(0, width - 36)).padEnd(Math.max(0, width - 36)), domainColor(domain.level)],
|
|
610
599
|
[' ', C.label],
|
|
611
600
|
[domain.next.slice(0, 20), C.dim],
|
|
612
601
|
]));
|
|
613
602
|
for (const detail of domain.details.slice(0, 2)) {
|
|
614
|
-
domainLines.push(...buildBodyText(width, ` ${detail}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
603
|
+
domainLines.push(...buildBodyText(width, ` ${detail}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.dim));
|
|
615
604
|
}
|
|
616
605
|
if (domain.nextSteps.length > 1) {
|
|
617
|
-
domainLines.push(...buildBodyText(width, ` next: ${domain.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
606
|
+
domainLines.push(...buildBodyText(width, ` next: ${domain.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.title));
|
|
618
607
|
}
|
|
619
608
|
}
|
|
620
609
|
|
|
@@ -632,61 +621,61 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
632
621
|
session: session.session,
|
|
633
622
|
});
|
|
634
623
|
maintenanceLines.push(buildKeyValueLine(width, [
|
|
635
|
-
{ label: 'level', value: maintenance.level, valueColor: maintenance.level === 'needs-repair' ? C.
|
|
624
|
+
{ label: 'level', value: maintenance.level, valueColor: maintenance.level === 'needs-repair' ? C.bad : maintenance.level === 'suggest-compact' || maintenance.level === 'watch' ? C.warn : C.good },
|
|
636
625
|
{ label: 'guidance', value: maintenance.guidanceMode, valueColor: C.value },
|
|
637
|
-
{ label: 'usage', value: `${maintenance.usagePct}%`, valueColor: maintenance.usagePct >= 80 ? C.
|
|
626
|
+
{ label: 'usage', value: `${maintenance.usagePct}%`, valueColor: maintenance.usagePct >= 80 ? C.bad : maintenance.usagePct >= 70 ? C.warn : C.value },
|
|
638
627
|
{ label: 'remaining', value: maintenance.remainingTokens.toLocaleString(), valueColor: C.value },
|
|
639
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
628
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }));
|
|
640
629
|
for (const reason of maintenance.reasons.slice(0, 3)) {
|
|
641
|
-
maintenanceLines.push(...buildBodyText(width, reason, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
630
|
+
maintenanceLines.push(...buildBodyText(width, reason, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.dim));
|
|
642
631
|
}
|
|
643
632
|
if (maintenance.nextSteps.length > 0) {
|
|
644
|
-
maintenanceLines.push(...buildBodyText(width, `Next: ${maintenance.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
633
|
+
maintenanceLines.push(...buildBodyText(width, `Next: ${maintenance.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.title));
|
|
645
634
|
}
|
|
646
635
|
if (selectedName) {
|
|
647
636
|
const status = selectedHealth?.status ?? 'unknown';
|
|
648
637
|
selectedLines.push(buildKeyValueLine(width, [
|
|
649
|
-
{ label: 'provider', value: selectedName, valueColor: C.
|
|
638
|
+
{ label: 'provider', value: selectedName, valueColor: C.value },
|
|
650
639
|
{ label: 'status', value: statusLabel(status), valueColor: statusDot(status).color },
|
|
651
640
|
{ label: 'last ok', value: fmtAgo(selectedHealth?.lastSuccessAt), valueColor: C.value },
|
|
652
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
641
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }));
|
|
653
642
|
if (selectedHealth?.rateLimitExpiresAt && selectedHealth.rateLimitExpiresAt > Date.now()) {
|
|
654
|
-
selectedLines.push(...buildBodyText(width, `Cooldown: ${fmtCooldown(selectedHealth.rateLimitExpiresAt)}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
643
|
+
selectedLines.push(...buildBodyText(width, `Cooldown: ${fmtCooldown(selectedHealth.rateLimitExpiresAt)}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.warn));
|
|
655
644
|
}
|
|
656
645
|
if (selectedHealth?.lastErrorMessage) {
|
|
657
|
-
selectedLines.push(...buildBodyText(width, `Last error: ${selectedHealth.lastErrorMessage}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
646
|
+
selectedLines.push(...buildBodyText(width, `Last error: ${selectedHealth.lastErrorMessage}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.bad));
|
|
658
647
|
}
|
|
659
648
|
if (selectedAccount) {
|
|
660
649
|
selectedLines.push(buildKeyValueLine(width, [
|
|
661
650
|
{ label: 'route', value: selectedAccount.activeRoute, valueColor: routeColor(selectedAccount.activeRoute) },
|
|
662
651
|
{ label: 'preferred', value: selectedAccount.preferredRoute, valueColor: C.dim },
|
|
663
652
|
{ label: 'freshness', value: selectedAccount.authFreshness, valueColor: freshnessColor(selectedAccount.authFreshness) },
|
|
664
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
653
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }));
|
|
665
654
|
selectedLines.push(buildKeyValueLine(width, [
|
|
666
655
|
{ 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
|
|
656
|
+
{ label: 'active', value: selectedAccount.active ? 'yes' : 'no', valueColor: selectedAccount.active ? C.good : C.dim },
|
|
657
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }));
|
|
658
|
+
selectedLines.push(...buildBodyText(width, `Auth route: ${selectedAccount.activeRouteReason}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.dim));
|
|
670
659
|
if (selectedAccount.fallbackRisk) {
|
|
671
|
-
selectedLines.push(...buildBodyText(width, `Fallback: ${selectedAccount.fallbackRisk}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
660
|
+
selectedLines.push(...buildBodyText(width, `Fallback: ${selectedAccount.fallbackRisk}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.warn));
|
|
672
661
|
}
|
|
673
662
|
if (selectedAccount.issues.length > 0) {
|
|
674
|
-
selectedLines.push(...buildBodyText(width, `Issue: ${selectedAccount.issues[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
663
|
+
selectedLines.push(...buildBodyText(width, `Issue: ${selectedAccount.issues[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.bad));
|
|
675
664
|
}
|
|
676
665
|
if (selectedAccount.recommendedActions.length > 0) {
|
|
677
|
-
selectedLines.push(...buildBodyText(width, `Next: ${selectedAccount.recommendedActions[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
666
|
+
selectedLines.push(...buildBodyText(width, `Next: ${selectedAccount.recommendedActions[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title }, C.title));
|
|
678
667
|
}
|
|
679
668
|
}
|
|
680
669
|
}
|
|
681
670
|
|
|
682
|
-
const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Health posture', postureLines, { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
671
|
+
const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Health posture', postureLines, { ...DEFAULT_PANEL_PALETTE, header: C.title }) };
|
|
683
672
|
const domainsSection: PanelWorkspaceSection = { title: 'Repair Domains', lines: domainLines };
|
|
684
673
|
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
|
|
674
|
+
const selectedSections = selectedLines.length > 0 ? [{ lines: buildDetailBlock(width, 'Selected provider', selectedLines, { ...DEFAULT_PANEL_PALETTE, header: C.title }) } satisfies PanelWorkspaceSection] : [];
|
|
686
675
|
const resolvedProvidersSection = resolvePrimaryScrollableSection(width, height, {
|
|
687
676
|
intro,
|
|
688
677
|
footerLines: [buildPanelLine(width, [[' j/k or Up/Down move live cooldowns refresh while active', C.dim]])],
|
|
689
|
-
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
678
|
+
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title },
|
|
690
679
|
beforeSections: [postureSection, domainsSection, ...maintenanceSections],
|
|
691
680
|
section: {
|
|
692
681
|
title: 'Providers',
|
|
@@ -696,13 +685,13 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
696
685
|
const latency = health?.lastLatencyMs !== undefined ? fmtMs(health.lastLatencyMs) : 'n/a';
|
|
697
686
|
const latencyFg = health?.lastLatencyMs !== undefined ? latencyColor(health.lastLatencyMs) : C.dim;
|
|
698
687
|
return buildPanelListRow(width, [
|
|
699
|
-
{ text: name.padEnd(16), fg: C.
|
|
688
|
+
{ text: name.padEnd(16), fg: C.value },
|
|
700
689
|
{ text: statusLabel(status).padEnd(14), fg: statusDot(status).color },
|
|
701
690
|
{ text: ' lat ', fg: C.label },
|
|
702
691
|
{ text: latency.padEnd(8), fg: latencyFg },
|
|
703
692
|
{ text: ' ok ', fg: C.label },
|
|
704
693
|
{ text: fmtAgo(health?.lastSuccessAt).padEnd(10), fg: C.value },
|
|
705
|
-
], { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
694
|
+
], { ...DEFAULT_PANEL_PALETTE, header: C.title }, { selected: absolute === this._selectedIndex, selectedBg: '#111827' });
|
|
706
695
|
}),
|
|
707
696
|
selectedIndex: this._selectedIndex,
|
|
708
697
|
scrollOffset: this._scrollOffset,
|
|
@@ -725,7 +714,7 @@ export class ProviderHealthPanel extends BasePanel {
|
|
|
725
714
|
intro,
|
|
726
715
|
sections,
|
|
727
716
|
footerLines: [buildPanelLine(width, [[' j/k or Up/Down move live cooldowns refresh while active', C.dim]])],
|
|
728
|
-
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title
|
|
717
|
+
palette: { ...DEFAULT_PANEL_PALETTE, header: C.title },
|
|
729
718
|
});
|
|
730
719
|
}
|
|
731
720
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
buildEmptyState,
|
|
9
9
|
buildPanelLine,
|
|
10
10
|
buildPanelWorkspace,
|
|
11
|
+
extendPalette,
|
|
11
12
|
resolveScrollablePanelSection,
|
|
12
13
|
DEFAULT_PANEL_PALETTE,
|
|
13
14
|
type PanelWorkspaceSection,
|
|
@@ -17,12 +18,9 @@ import {
|
|
|
17
18
|
// Colors
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
|
|
20
|
-
const C = {
|
|
21
|
-
header: '#00d7ff',
|
|
22
|
-
sectionHeader: '244',
|
|
21
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
23
22
|
enabled: '#5fd700',
|
|
24
23
|
disabled: '#6c6c6c',
|
|
25
|
-
selected: '#1c1c1c',
|
|
26
24
|
selectedFg: '#ffffff',
|
|
27
25
|
id: '238',
|
|
28
26
|
cron: '#af87ff',
|
|
@@ -32,8 +30,7 @@ const C = {
|
|
|
32
30
|
runCount: '#ffaf00',
|
|
33
31
|
statusRunning: '#5fd700',
|
|
34
32
|
statusFailed: '#ff5f5f',
|
|
35
|
-
|
|
36
|
-
} as const;
|
|
33
|
+
});
|
|
37
34
|
|
|
38
35
|
// ---------------------------------------------------------------------------
|
|
39
36
|
// View items
|
|
@@ -289,7 +286,7 @@ export class SchedulePanel extends BasePanel {
|
|
|
289
286
|
* Row 3: prompt preview [history]
|
|
290
287
|
*/
|
|
291
288
|
private renderTask(task: AutomationJob, history: AutomationRun[], selected: boolean, width: number): Line[] {
|
|
292
|
-
const bg = selected ? C.
|
|
289
|
+
const bg = selected ? C.selectBg : undefined;
|
|
293
290
|
const fgBase = selected ? C.selectedFg : undefined;
|
|
294
291
|
|
|
295
292
|
const bullet = task.enabled ? '* ' : 'o ';
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
buildPanelWorkspace,
|
|
16
16
|
resolveScrollablePanelSection,
|
|
17
17
|
DEFAULT_PANEL_PALETTE,
|
|
18
|
+
extendPalette,
|
|
18
19
|
type PanelWorkspaceSection,
|
|
19
20
|
} from './polish.ts';
|
|
20
21
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
@@ -28,24 +29,15 @@ import {
|
|
|
28
29
|
} from './search-focus.ts';
|
|
29
30
|
import { type ConfirmState, handleConfirmInput } from './confirm-state.ts';
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
label: '#8888bb',
|
|
41
|
-
value: '#ccccdd',
|
|
42
|
-
dateFg: '#6699aa',
|
|
43
|
-
modelFg: '#99aacc',
|
|
44
|
-
countFg: '#88bbcc',
|
|
45
|
-
warnFg: '#ffcc44',
|
|
46
|
-
errorFg: '#ff6666',
|
|
47
|
-
separator: '#333355',
|
|
48
|
-
} as const;
|
|
32
|
+
// Panel-specific extras only; shared tones come from DEFAULT_PANEL_PALETTE so
|
|
33
|
+
// theme changes propagate. `selected` is a deliberate bright-cyan cursor accent
|
|
34
|
+
// and the date/model/count column tints have no clean shared equivalent.
|
|
35
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
36
|
+
selected: '#00ffff',
|
|
37
|
+
dateFg: '#6699aa',
|
|
38
|
+
modelFg: '#99aacc',
|
|
39
|
+
countFg: '#88bbcc',
|
|
40
|
+
});
|
|
49
41
|
|
|
50
42
|
function shortDate(ts: number): string {
|
|
51
43
|
const d = new Date(ts);
|
|
@@ -308,7 +300,7 @@ export class SessionBrowserPanel extends BasePanel {
|
|
|
308
300
|
}
|
|
309
301
|
|
|
310
302
|
private _renderSession(width: number, sess: SessionInfo, isCursor: boolean): Line {
|
|
311
|
-
const bg = isCursor ? C.
|
|
303
|
+
const bg = isCursor ? C.selectBg : '';
|
|
312
304
|
const date = shortDate(sess.timestamp);
|
|
313
305
|
const cnt = String(sess.messageCount).padStart(3) + 'm ';
|
|
314
306
|
const model = (sess.model || 'unknown').slice(0, 18).padEnd(18) + ' ';
|
|
@@ -317,10 +309,10 @@ export class SessionBrowserPanel extends BasePanel {
|
|
|
317
309
|
return buildStyledPanelLine(width, [
|
|
318
310
|
{ text: isCursor ? '▸' : ' ', fg: C.selected, bg, bold: isCursor },
|
|
319
311
|
{ text: date, fg: C.dateFg, bg },
|
|
320
|
-
{ text: ' ', fg: C.
|
|
312
|
+
{ text: ' ', fg: C.value, bg },
|
|
321
313
|
{ text: cnt, fg: C.countFg, bg },
|
|
322
314
|
{ text: model, fg: C.modelFg, bg },
|
|
323
|
-
{ text: title, fg: isCursor ? C.selected : C.
|
|
315
|
+
{ text: title, fg: isCursor ? C.selected : C.value, bg, bold: isCursor },
|
|
324
316
|
]);
|
|
325
317
|
}
|
|
326
318
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
getPanelSearchFocusTransition,
|
|
17
17
|
isPanelSearchCancel,
|
|
18
18
|
} from './search-focus.ts';
|
|
19
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
19
20
|
|
|
20
21
|
const C = {
|
|
21
22
|
...DEFAULT_PANEL_PALETTE,
|
|
@@ -259,7 +260,7 @@ export class SkillsPanel extends SearchableListPanel<SkillRecord> {
|
|
|
259
260
|
this.invalidateFilter();
|
|
260
261
|
});
|
|
261
262
|
} catch (err) {
|
|
262
|
-
this.setError(
|
|
263
|
+
this.setError(summarizeError(err));
|
|
263
264
|
}
|
|
264
265
|
this.markDirty();
|
|
265
266
|
})();
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
buildPanelWorkspace,
|
|
15
15
|
DEFAULT_PANEL_PALETTE,
|
|
16
16
|
} from './polish.ts';
|
|
17
|
+
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
17
18
|
|
|
18
19
|
const C = {
|
|
19
20
|
...DEFAULT_PANEL_PALETTE,
|
|
@@ -35,13 +36,7 @@ function formatWhen(value?: number): string {
|
|
|
35
36
|
|
|
36
37
|
function formatDuration(startedAt?: number, endedAt?: number): string {
|
|
37
38
|
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`;
|
|
39
|
+
return formatElapsed(Math.max(0, (endedAt ?? Date.now()) - startedAt));
|
|
45
40
|
}
|
|
46
41
|
|
|
47
42
|
function kindLabel(kind: RuntimeTask['kind']): string {
|
|
@@ -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
19
|
import { truncateDisplay } 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;
|
|
@@ -195,7 +190,7 @@ export class ThinkingPanel extends BasePanel {
|
|
|
195
190
|
}
|
|
196
191
|
|
|
197
192
|
private _renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
|
|
198
|
-
const bg = isCursor ? C.
|
|
193
|
+
const bg = isCursor ? C.selectBg : '';
|
|
199
194
|
if (row.kind === 'header') {
|
|
200
195
|
const indicator = this.blocks[row.blockIndex]?.collapsed ? '▸' : '▾';
|
|
201
196
|
const active = this.blocks[row.blockIndex]?.active;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
buildPanelWorkspace,
|
|
13
13
|
resolveScrollablePanelSection,
|
|
14
14
|
DEFAULT_PANEL_PALETTE,
|
|
15
|
+
extendPalette,
|
|
15
16
|
type PanelWorkspaceSection,
|
|
16
17
|
} from './polish.ts';
|
|
17
18
|
|
|
@@ -32,22 +33,16 @@ interface TurnUsage {
|
|
|
32
33
|
// Colors
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
34
35
|
|
|
35
|
-
const C = {
|
|
36
|
-
|
|
36
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
37
|
+
// Token data-series colors (input/output/cache) — categorical, no shared equivalent
|
|
37
38
|
input: '#00ffff',
|
|
38
39
|
output: '#d000ff',
|
|
39
40
|
cacheRead: '#00d700',
|
|
40
41
|
cacheWrite: '#ffaf00',
|
|
42
|
+
// Progress-bar track shade and per-turn table sub-header gray
|
|
41
43
|
barBg: '236',
|
|
42
|
-
label: '244',
|
|
43
|
-
value: '252',
|
|
44
|
-
sectionHeader: '238',
|
|
45
|
-
warnYellow: '#ffaf00',
|
|
46
|
-
warnRed: '#ff5f5f',
|
|
47
|
-
dim: '240',
|
|
48
|
-
good: '#5fd700',
|
|
49
44
|
turnHeader: '242',
|
|
50
|
-
}
|
|
45
|
+
});
|
|
51
46
|
|
|
52
47
|
// Warning thresholds for context window usage
|
|
53
48
|
const WARN_YELLOW = 0.70;
|
|
@@ -99,11 +94,17 @@ export class TokenBudgetPanel extends BasePanel {
|
|
|
99
94
|
private sessionReadModel: UiReadModel<UiSessionSnapshot> | null = null;
|
|
100
95
|
private readonly sessionMemoryStore: SessionMemoryQuery;
|
|
101
96
|
private readonly configManager: Pick<ConfigManager, 'get'>;
|
|
97
|
+
private readonly requestRender: () => void;
|
|
102
98
|
|
|
103
|
-
constructor(
|
|
99
|
+
constructor(
|
|
100
|
+
sessionMemoryStore: SessionMemoryQuery,
|
|
101
|
+
configManager: Pick<ConfigManager, 'get'>,
|
|
102
|
+
requestRender: () => void = () => {},
|
|
103
|
+
) {
|
|
104
104
|
super('tokens', 'Tokens', 'T', 'monitoring');
|
|
105
105
|
this.sessionMemoryStore = sessionMemoryStore;
|
|
106
106
|
this.configManager = configManager;
|
|
107
|
+
this.requestRender = requestRender;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
// ---------------------------------------------------------------------------
|
|
@@ -149,7 +150,7 @@ export class TokenBudgetPanel extends BasePanel {
|
|
|
149
150
|
this.turnHistory.shift();
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
this.
|
|
153
|
+
this._refreshAndRender();
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
// ---------------------------------------------------------------------------
|
|
@@ -161,11 +162,17 @@ export class TokenBudgetPanel extends BasePanel {
|
|
|
161
162
|
this.refresh();
|
|
162
163
|
// Poll every 2 s while active so the context bar stays current during streaming
|
|
163
164
|
if (this.refreshTimer !== null) clearInterval(this.refreshTimer);
|
|
164
|
-
this.refreshTimer = setInterval(() => this.
|
|
165
|
+
this.refreshTimer = setInterval(() => this._refreshAndRender(), 2_000);
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
override onDeactivate(): void {
|
|
168
169
|
super.onDeactivate();
|
|
170
|
+
// Stop the off-screen poll so a backgrounded Tokens tab does not keep
|
|
171
|
+
// refreshing (and, once requestRender is wired, force a repaint) every 2 s.
|
|
172
|
+
if (this.refreshTimer !== null) {
|
|
173
|
+
clearInterval(this.refreshTimer);
|
|
174
|
+
this.refreshTimer = null;
|
|
175
|
+
}
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
override onDestroy(): void {
|
|
@@ -194,6 +201,15 @@ export class TokenBudgetPanel extends BasePanel {
|
|
|
194
201
|
this.markDirty();
|
|
195
202
|
}
|
|
196
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Refresh data and request a repaint. Used by the background poll and on
|
|
206
|
+
* turn completion, where no key event will otherwise drive a render.
|
|
207
|
+
*/
|
|
208
|
+
private _refreshAndRender(): void {
|
|
209
|
+
this.refresh();
|
|
210
|
+
this.requestRender();
|
|
211
|
+
}
|
|
212
|
+
|
|
197
213
|
// ---------------------------------------------------------------------------
|
|
198
214
|
// Rendering
|
|
199
215
|
// ---------------------------------------------------------------------------
|
|
@@ -369,10 +385,10 @@ export class TokenBudgetPanel extends BasePanel {
|
|
|
369
385
|
let barColor: string;
|
|
370
386
|
let warnSuffix = '';
|
|
371
387
|
if (pct >= WARN_RED) {
|
|
372
|
-
barColor = C.
|
|
388
|
+
barColor = C.bad;
|
|
373
389
|
warnSuffix = ' ! CRITICAL';
|
|
374
390
|
} else if (pct >= WARN_YELLOW) {
|
|
375
|
-
barColor = C.
|
|
391
|
+
barColor = C.warn;
|
|
376
392
|
warnSuffix = ' ! HIGH';
|
|
377
393
|
} else {
|
|
378
394
|
barColor = C.good;
|
|
@@ -13,26 +13,18 @@ import {
|
|
|
13
13
|
buildPanelWorkspace,
|
|
14
14
|
resolveScrollablePanelSection,
|
|
15
15
|
DEFAULT_PANEL_PALETTE,
|
|
16
|
+
extendPalette,
|
|
16
17
|
type PanelWorkspaceSection,
|
|
17
18
|
} from './polish.ts';
|
|
18
19
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
errorFg: '#ff6666',
|
|
28
|
-
durationFg: '#aaaa66',
|
|
29
|
-
argsFg: '#aaaaaa',
|
|
30
|
-
dimFg: '#555566',
|
|
31
|
-
selected: '#00ffff',
|
|
32
|
-
selectedBg: '#1a2a3a',
|
|
33
|
-
labelFg: '#8888bb',
|
|
34
|
-
filterFg: '#ffcc44',
|
|
35
|
-
} as const;
|
|
21
|
+
// Panel-specific accents only; shared tones come from DEFAULT_PANEL_PALETTE so
|
|
22
|
+
// theme changes propagate. selectedBg->selectBg, errorFg->bad are shared keys.
|
|
23
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
24
|
+
selected: '#00ffff',
|
|
25
|
+
toolFg: '#00ccff',
|
|
26
|
+
argsFg: '#aaaaaa',
|
|
27
|
+
});
|
|
36
28
|
|
|
37
29
|
const MAX_ENTRIES = 500;
|
|
38
30
|
|
|
@@ -271,7 +263,7 @@ export class ToolInspectorPanel extends BasePanel {
|
|
|
271
263
|
}
|
|
272
264
|
|
|
273
265
|
private _renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
|
|
274
|
-
const bg = isCursor ? C.
|
|
266
|
+
const bg = isCursor ? C.selectBg : '';
|
|
275
267
|
if (row.kind === 'call') {
|
|
276
268
|
return buildStyledPanelLine(width, [
|
|
277
269
|
{ text: isCursor ? '▸' : ' ', fg: C.selected, bg, bold: isCursor },
|
|
@@ -280,7 +272,7 @@ export class ToolInspectorPanel extends BasePanel {
|
|
|
280
272
|
}
|
|
281
273
|
return buildStyledPanelLine(width, [
|
|
282
274
|
{ text: ' ', fg: C.argsFg, bg },
|
|
283
|
-
{ text: truncateDisplay(row.text, Math.max(0, width - 2)), fg: row.isError ? C.
|
|
275
|
+
{ text: truncateDisplay(row.text, Math.max(0, width - 2)), fg: row.isError ? C.bad : C.argsFg, bg },
|
|
284
276
|
]);
|
|
285
277
|
}
|
|
286
278
|
|
|
@@ -2,6 +2,7 @@ import type { Line } from '../types/grid.ts';
|
|
|
2
2
|
import { UIFactory } from '../renderer/ui-factory.ts';
|
|
3
3
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
4
|
import type { WorkPlanItem, WorkPlanItemStatus, WorkPlanStore } from '../work-plans/work-plan-store.ts';
|
|
5
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
6
|
|
|
6
7
|
const STATUS_LABEL: Record<WorkPlanItemStatus, string> = {
|
|
7
8
|
pending: '[ ]',
|
|
@@ -98,7 +99,7 @@ export class WorkPlanPanel extends ScrollableListPanel<WorkPlanItem> {
|
|
|
98
99
|
return super.handleInput(key);
|
|
99
100
|
}
|
|
100
101
|
} catch (error) {
|
|
101
|
-
this.setError(
|
|
102
|
+
this.setError(summarizeError(error));
|
|
102
103
|
return true;
|
|
103
104
|
}
|
|
104
105
|
}
|