@pellux/goodvibes-tui 1.1.0 → 1.9.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 +85 -21
- package/README.md +28 -18
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
package/src/panels/eval-panel.ts
DELETED
|
@@ -1,627 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Eval Panel — renders evaluation harness results in list and detail modes.
|
|
3
|
-
*
|
|
4
|
-
* Displays suite run summaries, per-scenario scorecards, and regression
|
|
5
|
-
* indicators. Wired with an EvalRegistry that holds the latest run results.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
import { BasePanel } from './base-panel.ts';
|
|
10
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
11
|
-
import type { Line } from '../types/grid.ts';
|
|
12
|
-
import type { KeyName, PanelIntegrationContext } from './types.ts';
|
|
13
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
14
|
-
import {
|
|
15
|
-
buildAlignedRow,
|
|
16
|
-
buildKeyboardHints,
|
|
17
|
-
buildMeterLine,
|
|
18
|
-
buildPanelLine,
|
|
19
|
-
buildPanelWorkspace,
|
|
20
|
-
buildTable,
|
|
21
|
-
resolveScrollablePanelSection,
|
|
22
|
-
DEFAULT_PANEL_PALETTE,
|
|
23
|
-
extendPalette,
|
|
24
|
-
} from './polish.ts';
|
|
25
|
-
|
|
26
|
-
// ── EvalRegistry ─────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
import type {
|
|
29
|
-
EvalSuiteResult,
|
|
30
|
-
EvalResult,
|
|
31
|
-
EvalGateResult,
|
|
32
|
-
EvalDimension,
|
|
33
|
-
EvalBaseline,
|
|
34
|
-
RegressionEntry,
|
|
35
|
-
} from '@/runtime/index.ts';
|
|
36
|
-
import { loadBaseline } from '@/runtime/index.ts';
|
|
37
|
-
import { formatShortDuration } from '../utils/format-duration.ts';
|
|
38
|
-
|
|
39
|
-
const BASELINE_PATH = '.goodvibes/eval/baseline.json';
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Holds the latest eval run state for display in EvalPanel.
|
|
43
|
-
* Created externally, injected into the panel.
|
|
44
|
-
*/
|
|
45
|
-
export class EvalRegistry {
|
|
46
|
-
private _suiteResults: EvalSuiteResult[] = [];
|
|
47
|
-
private _gateResults: EvalGateResult[] = [];
|
|
48
|
-
private _running = false;
|
|
49
|
-
private _lastRunAt: number | null = null;
|
|
50
|
-
private readonly _subscribers = new Set<() => void>();
|
|
51
|
-
|
|
52
|
-
push(result: EvalSuiteResult): void {
|
|
53
|
-
const idx = this._suiteResults.findIndex((r) => r.suite === result.suite);
|
|
54
|
-
if (idx >= 0) {
|
|
55
|
-
this._suiteResults[idx] = result;
|
|
56
|
-
} else {
|
|
57
|
-
this._suiteResults.push(result);
|
|
58
|
-
}
|
|
59
|
-
this._lastRunAt = Date.now();
|
|
60
|
-
this._notify();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
pushGate(gate: EvalGateResult): void {
|
|
64
|
-
const idx = this._gateResults.findIndex((g) => g.suite === gate.suite);
|
|
65
|
-
if (idx >= 0) {
|
|
66
|
-
this._gateResults[idx] = gate;
|
|
67
|
-
} else {
|
|
68
|
-
this._gateResults.push(gate);
|
|
69
|
-
}
|
|
70
|
-
this._notify();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
setRunning(running: boolean): void {
|
|
74
|
-
this._running = running;
|
|
75
|
-
this._notify();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
isRunning(): boolean { return this._running; }
|
|
79
|
-
getLastRunAt(): number | null { return this._lastRunAt; }
|
|
80
|
-
getSuiteResults(): EvalSuiteResult[] { return this._suiteResults; }
|
|
81
|
-
getGateResults(): EvalGateResult[] { return this._gateResults; }
|
|
82
|
-
|
|
83
|
-
subscribe(cb: () => void): () => void {
|
|
84
|
-
this._subscribers.add(cb);
|
|
85
|
-
return () => this._subscribers.delete(cb);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private _notify(): void {
|
|
89
|
-
for (const cb of this._subscribers) cb();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ── Colour palette (no inline hex in render bodies) ──────────────────────────
|
|
94
|
-
|
|
95
|
-
// Domain accents only; base chrome (header/headerBg/info/good/warn/bad/
|
|
96
|
-
// value/selectBg) comes from DEFAULT_PANEL_PALETTE.
|
|
97
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
98
|
-
selected: '#f1f5f9', // emphasized row/text highlight, brighter than value
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
102
|
-
|
|
103
|
-
function scoreColor(score: number): string {
|
|
104
|
-
if (score >= 80) return C.good;
|
|
105
|
-
if (score >= 60) return C.warn;
|
|
106
|
-
return C.bad;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function fmtTime(ms: number): string {
|
|
110
|
-
return formatShortDuration(ms);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const DIMENSION_ORDER: EvalDimension[] = ['safety', 'quality', 'latency', 'cost', 'recovery'];
|
|
114
|
-
|
|
115
|
-
// ── EvalPanel ─────────────────────────────────────────────────────────────────
|
|
116
|
-
|
|
117
|
-
export class EvalPanel extends BasePanel {
|
|
118
|
-
private readonly _registry: EvalRegistry;
|
|
119
|
-
private readonly _projectRoot: string;
|
|
120
|
-
private _mode: 'list' | 'detail' = 'list';
|
|
121
|
-
private _selectedSuiteIdx = 0;
|
|
122
|
-
private _selectedScenarioIdx = 0;
|
|
123
|
-
private _scrollOffset = 0;
|
|
124
|
-
private _unsub: (() => void) | null = null;
|
|
125
|
-
private _baseline: EvalBaseline | null = null;
|
|
126
|
-
private _baselineLoading = false;
|
|
127
|
-
|
|
128
|
-
public constructor(registry: EvalRegistry, projectRoot = '.') {
|
|
129
|
-
super('eval', 'Eval', '▮', 'incidents-diagnostics');
|
|
130
|
-
this._registry = registry;
|
|
131
|
-
this._projectRoot = projectRoot;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public override onActivate(): void {
|
|
135
|
-
this._unsub = this._registry.subscribe(() => this.markDirty());
|
|
136
|
-
this._loadBaselineIfNeeded();
|
|
137
|
-
this.markDirty();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public override onDestroy(): void {
|
|
141
|
-
this._unsub?.();
|
|
142
|
-
this._unsub = null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Seed the panel with last-known scores from the on-disk baseline so a
|
|
147
|
-
* fresh session shows real data instead of a "run a suite" signpost.
|
|
148
|
-
* Only loaded once per activation; a live suite run supersedes it.
|
|
149
|
-
*
|
|
150
|
-
* Resolved as an absolute path under _projectRoot before handing it to
|
|
151
|
-
* loadBaseline() — the SDK helper resolves a relative path against the
|
|
152
|
-
* process cwd (not the projectRoot argument), so joining here is what
|
|
153
|
-
* actually makes the constructor's projectRoot meaningful.
|
|
154
|
-
*/
|
|
155
|
-
private _loadBaselineIfNeeded(): void {
|
|
156
|
-
if (this._baseline || this._baselineLoading) return;
|
|
157
|
-
this._baselineLoading = true;
|
|
158
|
-
void loadBaseline(join(this._projectRoot, BASELINE_PATH), this._projectRoot)
|
|
159
|
-
.then((baseline) => {
|
|
160
|
-
this._baseline = baseline ?? null;
|
|
161
|
-
})
|
|
162
|
-
.catch(() => {
|
|
163
|
-
this._baseline = null;
|
|
164
|
-
})
|
|
165
|
-
.finally(() => {
|
|
166
|
-
this._baselineLoading = false;
|
|
167
|
-
this.markDirty();
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Number of selectable rows in list mode: live suite results, or baseline suites when none have run yet. */
|
|
172
|
-
private _listLength(suites: EvalSuiteResult[]): number {
|
|
173
|
-
if (suites.length > 0) return suites.length;
|
|
174
|
-
return this._baseline ? Object.keys(this._baseline.suites).length : 0;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** Name of the suite currently selected in list mode, from live results or the baseline. */
|
|
178
|
-
private _selectedSuiteName(suites: EvalSuiteResult[]): string | undefined {
|
|
179
|
-
if (suites.length > 0) return suites[this._selectedSuiteIdx]?.suite;
|
|
180
|
-
if (!this._baseline) return undefined;
|
|
181
|
-
return Object.keys(this._baseline.suites)[this._selectedSuiteIdx];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Total scenario/detail line count for the given suite, used to clamp pagedown scrolling. */
|
|
185
|
-
private _detailLineCount(suite: EvalSuiteResult): number {
|
|
186
|
-
const gates = this._registry.getGateResults();
|
|
187
|
-
const gate = gates.find((g) => g.suite === suite.suite);
|
|
188
|
-
const regressedIds = new Set((gate?.regressions ?? []).map((r) => r.scenarioId));
|
|
189
|
-
|
|
190
|
-
let count = 0;
|
|
191
|
-
suite.results.forEach((result, idx) => {
|
|
192
|
-
count += 1;
|
|
193
|
-
if (idx === this._selectedScenarioIdx) {
|
|
194
|
-
count += DIMENSION_ORDER.filter((dim) =>
|
|
195
|
-
result.scorecard.dimensions.some((d) => d.dimension === dim),
|
|
196
|
-
).length;
|
|
197
|
-
if (regressedIds.has(result.scenario.id)) count += 1;
|
|
198
|
-
count += result.scorecard.notes?.length ?? 0;
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
return count;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
public handleInput(key: KeyName): boolean {
|
|
205
|
-
const suites = this._registry.getSuiteResults();
|
|
206
|
-
|
|
207
|
-
if (this._mode === 'list') {
|
|
208
|
-
if (key === 'up' || key === 'k') {
|
|
209
|
-
this._selectedSuiteIdx = Math.max(0, this._selectedSuiteIdx - 1);
|
|
210
|
-
this.markDirty();
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
if (key === 'down' || key === 'j') {
|
|
214
|
-
const len = this._listLength(suites);
|
|
215
|
-
this._selectedSuiteIdx = Math.min(Math.max(0, len - 1), this._selectedSuiteIdx + 1);
|
|
216
|
-
this.markDirty();
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
219
|
-
if ((key === 'enter' || key === 'return' || key === 'l') && suites.length > 0) {
|
|
220
|
-
this._mode = 'detail';
|
|
221
|
-
this._selectedScenarioIdx = 0;
|
|
222
|
-
this._scrollOffset = 0;
|
|
223
|
-
this.markDirty();
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// detail mode
|
|
230
|
-
if (key === 'escape' || key === 'q' || key === 'h') {
|
|
231
|
-
this._mode = 'list';
|
|
232
|
-
this.markDirty();
|
|
233
|
-
return true;
|
|
234
|
-
}
|
|
235
|
-
if (key === 'up' || key === 'k') {
|
|
236
|
-
const suite = suites[this._selectedSuiteIdx];
|
|
237
|
-
if (suite) {
|
|
238
|
-
this._selectedScenarioIdx = Math.max(0, this._selectedScenarioIdx - 1);
|
|
239
|
-
this._scrollOffset = 0;
|
|
240
|
-
this.markDirty();
|
|
241
|
-
}
|
|
242
|
-
return true;
|
|
243
|
-
}
|
|
244
|
-
if (key === 'down' || key === 'j') {
|
|
245
|
-
const suite = suites[this._selectedSuiteIdx];
|
|
246
|
-
if (suite) {
|
|
247
|
-
this._selectedScenarioIdx = Math.min(
|
|
248
|
-
suite.results.length - 1,
|
|
249
|
-
this._selectedScenarioIdx + 1,
|
|
250
|
-
);
|
|
251
|
-
this._scrollOffset = 0;
|
|
252
|
-
this.markDirty();
|
|
253
|
-
}
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
if (key === 'pageup') {
|
|
257
|
-
this._scrollOffset = Math.max(0, this._scrollOffset - 5);
|
|
258
|
-
this.markDirty();
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
if (key === 'pagedown') {
|
|
262
|
-
const suite = suites[this._selectedSuiteIdx];
|
|
263
|
-
const maxOffset = suite ? Math.max(0, this._detailLineCount(suite) - 1) : 0;
|
|
264
|
-
this._scrollOffset = Math.min(maxOffset, this._scrollOffset + 5);
|
|
265
|
-
this.markDirty();
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Cross-panel integration hook: r runs the selected suite, R runs all
|
|
273
|
-
* suites, both dispatched through the real /eval command executor rather
|
|
274
|
-
* than a printed signpost. registry.setRunning()/push() (already wired in
|
|
275
|
-
* src/input/commands/eval.ts) animate the in-panel state as the run proceeds.
|
|
276
|
-
*/
|
|
277
|
-
public handlePanelIntegrationAction(key: string, ctx: PanelIntegrationContext): boolean {
|
|
278
|
-
if (key !== 'r' && key !== 'R') return false;
|
|
279
|
-
if (!ctx.executeCommand) return false;
|
|
280
|
-
|
|
281
|
-
if (key === 'R') {
|
|
282
|
-
void ctx.executeCommand('eval', ['run', 'all']).catch(() => {});
|
|
283
|
-
return true;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const suites = this._registry.getSuiteResults();
|
|
287
|
-
const suiteName = this._selectedSuiteName(suites);
|
|
288
|
-
if (!suiteName) return false;
|
|
289
|
-
void ctx.executeCommand('eval', ['run', suiteName]).catch(() => {});
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
public render(width: number, height: number): Line[] {
|
|
294
|
-
this.needsRender = false;
|
|
295
|
-
const suites = this._registry.getSuiteResults();
|
|
296
|
-
const gates = this._registry.getGateResults();
|
|
297
|
-
const intro = 'Evaluation harness runs, gates, scenario scorecards, and regression indicators for model and product validation.';
|
|
298
|
-
|
|
299
|
-
if (suites.length === 0) {
|
|
300
|
-
if (this._baseline && Object.keys(this._baseline.suites).length > 0) {
|
|
301
|
-
const workspace = this._renderBaselineList(this._baseline, width, height, intro);
|
|
302
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
303
|
-
return workspace;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
307
|
-
title: 'Eval Harness',
|
|
308
|
-
intro,
|
|
309
|
-
sections: [{
|
|
310
|
-
title: 'Status',
|
|
311
|
-
lines: [
|
|
312
|
-
this._statusLine(width),
|
|
313
|
-
buildPanelLine(width, [[' No results yet — no baseline on disk.', C.dim]]),
|
|
314
|
-
],
|
|
315
|
-
}],
|
|
316
|
-
footerLines: [buildKeyboardHints(width, [
|
|
317
|
-
{ keys: 'r', label: 'run selected suite' },
|
|
318
|
-
{ keys: 'R', label: 'run all suites' },
|
|
319
|
-
], C)],
|
|
320
|
-
palette: C,
|
|
321
|
-
});
|
|
322
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
323
|
-
return workspace;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const lines: Line[] = [];
|
|
327
|
-
if (this._mode === 'list') {
|
|
328
|
-
this._renderList(lines, suites, gates, width, height, intro);
|
|
329
|
-
} else {
|
|
330
|
-
const suite = suites[this._selectedSuiteIdx];
|
|
331
|
-
if (suite) {
|
|
332
|
-
this._renderDetail(lines, suite, gates, width, height, intro);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return lines;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// ── Shared summary line ───────────────────────────────────────────────────
|
|
340
|
-
|
|
341
|
-
private _statusLine(width: number): Line {
|
|
342
|
-
const running = this._registry.isRunning();
|
|
343
|
-
const lastRun = this._registry.getLastRunAt();
|
|
344
|
-
const suites = this._registry.getSuiteResults();
|
|
345
|
-
const passed = suites.filter((s) => s.passed).length;
|
|
346
|
-
const failed = suites.length - passed;
|
|
347
|
-
return buildPanelLine(width, [
|
|
348
|
-
[' state ', C.label],
|
|
349
|
-
[running ? 'running' : 'idle', running ? C.warn : C.dim],
|
|
350
|
-
[' passed ', C.label],
|
|
351
|
-
[String(passed), passed > 0 ? C.good : C.dim],
|
|
352
|
-
[' failed ', C.label],
|
|
353
|
-
[String(failed), failed > 0 ? C.bad : C.dim],
|
|
354
|
-
[' last ', C.label],
|
|
355
|
-
[lastRun ? new Date(lastRun).toLocaleTimeString() : 'n/a', C.dim],
|
|
356
|
-
]);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ── List view ────────────────────────────────────────────────────────────────
|
|
360
|
-
|
|
361
|
-
private _renderList(
|
|
362
|
-
lines: Line[],
|
|
363
|
-
suites: EvalSuiteResult[],
|
|
364
|
-
gates: EvalGateResult[],
|
|
365
|
-
width: number,
|
|
366
|
-
height: number,
|
|
367
|
-
intro: string,
|
|
368
|
-
): void {
|
|
369
|
-
const gateMap = new Map(gates.map((g) => [g.suite, g]));
|
|
370
|
-
|
|
371
|
-
const tableRows = suites.map((suite, idx) => {
|
|
372
|
-
const selected = idx === this._selectedSuiteIdx;
|
|
373
|
-
const gate = gateMap.get(suite.suite);
|
|
374
|
-
const gateStr = gate ? (gate.passed ? 'ok' : 'FAIL') : '-';
|
|
375
|
-
const gateColor = gate ? (gate.passed ? C.good : C.bad) : C.dim;
|
|
376
|
-
const durationMs = suite.finishedAt - suite.startedAt;
|
|
377
|
-
return {
|
|
378
|
-
selected,
|
|
379
|
-
cells: [
|
|
380
|
-
{ text: suite.suite, fg: selected ? C.selected : C.value, bold: selected },
|
|
381
|
-
{ text: suite.meanScore.toFixed(1), fg: scoreColor(suite.meanScore) },
|
|
382
|
-
{ text: suite.passed ? 'PASS' : 'FAIL', fg: suite.passed ? C.good : C.bad },
|
|
383
|
-
{ text: gateStr, fg: gateColor },
|
|
384
|
-
{ text: fmtTime(durationMs), fg: C.dim },
|
|
385
|
-
],
|
|
386
|
-
};
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
const tableLines = buildTable(width, [
|
|
390
|
-
{ label: 'Suite' },
|
|
391
|
-
{ label: 'Score', width: 7, align: 'right' },
|
|
392
|
-
{ label: 'Pass', width: 6 },
|
|
393
|
-
{ label: 'Gate', width: 6 },
|
|
394
|
-
{ label: 'Duration', width: 10, align: 'right' },
|
|
395
|
-
], tableRows, C, { selectedBg: C.selectBg });
|
|
396
|
-
|
|
397
|
-
const selectedSuite = suites[this._selectedSuiteIdx];
|
|
398
|
-
const detailLines: Line[] = [];
|
|
399
|
-
if (selectedSuite) {
|
|
400
|
-
const passCount = selectedSuite.results.filter((r) => r.scorecard.passed).length;
|
|
401
|
-
detailLines.push(buildPanelLine(width, [
|
|
402
|
-
[' selected ', C.label],
|
|
403
|
-
[selectedSuite.suite, C.info],
|
|
404
|
-
[' scenarios ', C.label],
|
|
405
|
-
[`${passCount}/${selectedSuite.results.length} passed`, passCount === selectedSuite.results.length ? C.good : C.warn],
|
|
406
|
-
]));
|
|
407
|
-
const meterW = Math.max(10, Math.min(30, width - 24));
|
|
408
|
-
detailLines.push(buildMeterLine(width, Math.round((selectedSuite.meanScore / 100) * meterW), meterW, {
|
|
409
|
-
filled: scoreColor(selectedSuite.meanScore),
|
|
410
|
-
empty: C.empty,
|
|
411
|
-
label: C.label,
|
|
412
|
-
}, { prefix: ' mean ', suffix: ` ${selectedSuite.meanScore.toFixed(1)}/100 ` }));
|
|
413
|
-
|
|
414
|
-
const gate = gateMap.get(selectedSuite.suite);
|
|
415
|
-
if (gate && gate.regressions.length > 0) {
|
|
416
|
-
const worst = [...gate.regressions].sort((a, b) => a.delta - b.delta)[0];
|
|
417
|
-
const segments: Array<[string, string]> = [
|
|
418
|
-
[' regressions ', C.label],
|
|
419
|
-
[`${gate.regressions.length}`, C.bad],
|
|
420
|
-
];
|
|
421
|
-
if (worst) {
|
|
422
|
-
segments.push([' worst ', C.label], [`${worst.scenarioName} ${worst.delta.toFixed(1)}`, C.bad]);
|
|
423
|
-
}
|
|
424
|
-
detailLines.push(buildPanelLine(width, segments));
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const footer = buildKeyboardHints(width, [
|
|
429
|
-
{ keys: `${this._selectedSuiteIdx + 1}/${suites.length}`, label: 'suite' },
|
|
430
|
-
{ keys: '↑/↓', label: 'navigate' },
|
|
431
|
-
{ keys: 'Enter', label: 'open detail' },
|
|
432
|
-
{ keys: 'r', label: 'run selected' },
|
|
433
|
-
{ keys: 'R', label: 'run all' },
|
|
434
|
-
], C);
|
|
435
|
-
|
|
436
|
-
lines.push(...buildPanelWorkspace(width, height, {
|
|
437
|
-
title: 'Eval Harness',
|
|
438
|
-
intro,
|
|
439
|
-
sections: [
|
|
440
|
-
{ lines: [this._statusLine(width)] },
|
|
441
|
-
{ title: 'Suites', lines: tableLines },
|
|
442
|
-
...(detailLines.length > 0 ? [{ title: 'Selected Suite', lines: detailLines }] : []),
|
|
443
|
-
],
|
|
444
|
-
footerLines: [footer],
|
|
445
|
-
palette: C,
|
|
446
|
-
}));
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// ── Detail view ──────────────────────────────────────────────────────────────
|
|
450
|
-
|
|
451
|
-
private _renderDetail(
|
|
452
|
-
lines: Line[],
|
|
453
|
-
suite: EvalSuiteResult,
|
|
454
|
-
gates: EvalGateResult[],
|
|
455
|
-
width: number,
|
|
456
|
-
height: number,
|
|
457
|
-
intro: string,
|
|
458
|
-
): void {
|
|
459
|
-
const passCount = suite.results.filter((r) => r.scorecard.passed).length;
|
|
460
|
-
const gate = gates.find((g) => g.suite === suite.suite);
|
|
461
|
-
const regressionMap = new Map((gate?.regressions ?? []).map((r) => [r.scenarioId, r]));
|
|
462
|
-
|
|
463
|
-
const headerLines: Line[] = [
|
|
464
|
-
this._statusLine(width),
|
|
465
|
-
buildPanelLine(width, [
|
|
466
|
-
[' suite ', C.label],
|
|
467
|
-
[suite.suite, C.info],
|
|
468
|
-
[' mean ', C.label],
|
|
469
|
-
[suite.meanScore.toFixed(1), scoreColor(suite.meanScore)],
|
|
470
|
-
[' ', C.label],
|
|
471
|
-
[suite.passed ? 'PASS' : 'FAIL', suite.passed ? C.good : C.bad],
|
|
472
|
-
[' scenarios ', C.label],
|
|
473
|
-
[`${passCount}/${suite.results.length} passed`, passCount === suite.results.length ? C.good : C.warn],
|
|
474
|
-
]),
|
|
475
|
-
];
|
|
476
|
-
if (gate && gate.regressions.length > 0) {
|
|
477
|
-
headerLines.push(buildPanelLine(width, [
|
|
478
|
-
[' regressions ', C.label],
|
|
479
|
-
[`${gate.regressions.length} scenario(s) below baseline`, C.bad],
|
|
480
|
-
]));
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const allDetailLines: Line[] = [];
|
|
484
|
-
suite.results.forEach((result, idx) => {
|
|
485
|
-
const selected = idx === this._selectedScenarioIdx;
|
|
486
|
-
this._renderScenarioBlock(allDetailLines, result, selected, width, regressionMap.get(result.scenario.id));
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const footer = buildKeyboardHints(width, [
|
|
490
|
-
{ keys: `${this._selectedScenarioIdx + 1}/${suite.results.length}`, label: 'scenario' },
|
|
491
|
-
{ keys: 'Esc', label: 'back' },
|
|
492
|
-
{ keys: '↑/↓', label: 'navigate' },
|
|
493
|
-
{ keys: 'PgUp/PgDn', label: 'scroll' },
|
|
494
|
-
], C);
|
|
495
|
-
|
|
496
|
-
const detailSection = resolveScrollablePanelSection(width, height, {
|
|
497
|
-
intro,
|
|
498
|
-
footerLines: [footer],
|
|
499
|
-
palette: C,
|
|
500
|
-
beforeSections: [{ title: `Scenario Detail · ${suite.suite}`, lines: headerLines }],
|
|
501
|
-
section: {
|
|
502
|
-
scrollableLines: allDetailLines,
|
|
503
|
-
scrollOffset: this._scrollOffset,
|
|
504
|
-
minRows: 1,
|
|
505
|
-
appendWindowSummary: { dimColor: C.dim },
|
|
506
|
-
},
|
|
507
|
-
});
|
|
508
|
-
this._scrollOffset = detailSection.scrollOffset;
|
|
509
|
-
|
|
510
|
-
lines.push(...buildPanelWorkspace(width, height, {
|
|
511
|
-
title: 'Eval Harness',
|
|
512
|
-
intro,
|
|
513
|
-
sections: [{ title: `Scenario Detail · ${suite.suite}`, lines: [...headerLines, ...detailSection.section.lines] }],
|
|
514
|
-
footerLines: [footer],
|
|
515
|
-
palette: C,
|
|
516
|
-
}));
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
private _renderScenarioBlock(
|
|
520
|
-
lines: Line[],
|
|
521
|
-
result: EvalResult,
|
|
522
|
-
selected: boolean,
|
|
523
|
-
width: number,
|
|
524
|
-
regression: RegressionEntry | undefined,
|
|
525
|
-
): void {
|
|
526
|
-
const sc = result.scorecard;
|
|
527
|
-
const scoreC = scoreColor(sc.compositeScore);
|
|
528
|
-
const passC = sc.passed ? C.good : C.bad;
|
|
529
|
-
const nameText = regression ? `▼ ${result.scenario.name}` : result.scenario.name;
|
|
530
|
-
const nameColor = selected ? C.selected : (regression ? C.bad : C.value);
|
|
531
|
-
|
|
532
|
-
lines.push(buildAlignedRow(
|
|
533
|
-
width,
|
|
534
|
-
[
|
|
535
|
-
{ text: nameText, fg: nameColor, bold: selected },
|
|
536
|
-
{ text: sc.compositeScore.toFixed(1), fg: scoreC },
|
|
537
|
-
{ text: sc.passed ? 'PASS' : 'FAIL', fg: passC },
|
|
538
|
-
],
|
|
539
|
-
[
|
|
540
|
-
{ width: Math.max(8, width - 16) },
|
|
541
|
-
{ width: 6, align: 'right' },
|
|
542
|
-
{ width: 5 },
|
|
543
|
-
],
|
|
544
|
-
{ gap: 1, selected, selectedBg: C.selectBg, marker: '▸' },
|
|
545
|
-
));
|
|
546
|
-
|
|
547
|
-
if (selected) {
|
|
548
|
-
for (const dim of DIMENSION_ORDER) {
|
|
549
|
-
const d = sc.dimensions.find((x) => x.dimension === dim);
|
|
550
|
-
if (!d) continue;
|
|
551
|
-
const filled = Math.round(d.score / 10);
|
|
552
|
-
lines.push(buildMeterLine(width, filled, 10, {
|
|
553
|
-
filled: scoreColor(d.score),
|
|
554
|
-
empty: C.empty,
|
|
555
|
-
label: C.label,
|
|
556
|
-
}, { prefix: ` ${dim.padEnd(9)} `, suffix: ` ${d.score.toFixed(0).padStart(3)}/100 ` }));
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (regression) {
|
|
560
|
-
lines.push(buildPanelLine(width, [
|
|
561
|
-
[' ▼ regression vs baseline ', C.bad],
|
|
562
|
-
[`${regression.baselineScore.toFixed(1)} -> ${regression.freshScore.toFixed(1)} (${regression.delta >= 0 ? '+' : ''}${regression.delta.toFixed(1)})`, C.bad],
|
|
563
|
-
]));
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
if (sc.notes && sc.notes.length > 0) {
|
|
567
|
-
for (const note of sc.notes) {
|
|
568
|
-
lines.push(buildPanelLine(width, [
|
|
569
|
-
[' ! ', C.warn],
|
|
570
|
-
[truncateDisplay(note, Math.max(8, width - 6)), C.warn],
|
|
571
|
-
]));
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// ── Baseline-seeded list (no live suite results yet) ─────────────────────────
|
|
578
|
-
|
|
579
|
-
private _renderBaselineList(
|
|
580
|
-
baseline: EvalBaseline,
|
|
581
|
-
width: number,
|
|
582
|
-
height: number,
|
|
583
|
-
intro: string,
|
|
584
|
-
): Line[] {
|
|
585
|
-
const entries = Object.entries(baseline.suites);
|
|
586
|
-
|
|
587
|
-
const tableRows = entries.map(([name, summary], idx) => {
|
|
588
|
-
const selected = idx === this._selectedSuiteIdx;
|
|
589
|
-
const scenarioCount = Object.keys(summary.scenarioScores).length;
|
|
590
|
-
return {
|
|
591
|
-
selected,
|
|
592
|
-
cells: [
|
|
593
|
-
{ text: name, fg: selected ? C.selected : C.value, bold: selected },
|
|
594
|
-
{ text: summary.meanScore.toFixed(1), fg: scoreColor(summary.meanScore) },
|
|
595
|
-
{ text: `${scenarioCount}`, fg: C.dim },
|
|
596
|
-
],
|
|
597
|
-
};
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
const tableLines = buildTable(width, [
|
|
601
|
-
{ label: 'Suite' },
|
|
602
|
-
{ label: 'Score', width: 7, align: 'right' },
|
|
603
|
-
{ label: 'Scenarios', width: 10, align: 'right' },
|
|
604
|
-
], tableRows, C, { selectedBg: C.selectBg });
|
|
605
|
-
|
|
606
|
-
const footer = buildKeyboardHints(width, [
|
|
607
|
-
{ keys: `${entries.length > 0 ? this._selectedSuiteIdx + 1 : 0}/${entries.length}`, label: 'suite' },
|
|
608
|
-
{ keys: '↑/↓', label: 'navigate' },
|
|
609
|
-
{ keys: 'r', label: 'run selected' },
|
|
610
|
-
{ keys: 'R', label: 'run all' },
|
|
611
|
-
], C);
|
|
612
|
-
|
|
613
|
-
return buildPanelWorkspace(width, height, {
|
|
614
|
-
title: 'Eval Harness',
|
|
615
|
-
intro,
|
|
616
|
-
sections: [
|
|
617
|
-
{ lines: [this._statusLine(width)] },
|
|
618
|
-
{
|
|
619
|
-
title: `Baseline · ${baseline.label} (captured ${new Date(baseline.capturedAt).toLocaleString()})`,
|
|
620
|
-
lines: tableLines,
|
|
621
|
-
},
|
|
622
|
-
],
|
|
623
|
-
footerLines: [footer],
|
|
624
|
-
palette: C,
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
}
|