@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
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpsControlPanel — operator control plane UI panel.
|
|
3
|
-
*
|
|
4
|
-
* Renders the ops audit log sourced from the OpsPanel diagnostics subscriber.
|
|
5
|
-
* Each entry shows: seq, timestamp, action, target, outcome, and optional note.
|
|
6
|
-
*
|
|
7
|
-
* Requires the `operator-control-plane` feature flag to be enabled.
|
|
8
|
-
* Open via Ctrl+O keybind or `/ops view` command.
|
|
9
|
-
*/
|
|
10
|
-
import type { Line } from '../types/grid.ts';
|
|
11
|
-
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
12
|
-
import type { OpsApi, OpsEvent } from '@/runtime/index.ts';
|
|
13
|
-
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
14
|
-
import type { OpsAuditEntry } from '../runtime/diagnostics/panels/ops.ts';
|
|
15
|
-
import { OpsPanel } from '../runtime/diagnostics/panels/ops.ts';
|
|
16
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
17
|
-
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
18
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
19
|
-
import {
|
|
20
|
-
buildKeyValueLine,
|
|
21
|
-
buildKeyboardHints,
|
|
22
|
-
buildPanelLine,
|
|
23
|
-
DEFAULT_PANEL_PALETTE,
|
|
24
|
-
extendPalette,
|
|
25
|
-
type PanelPalette,
|
|
26
|
-
} from './polish.ts';
|
|
27
|
-
|
|
28
|
-
// ── Colour palette ──────────────────────────────────────────────────────────
|
|
29
|
-
// Domain accents only; base chrome (header/headerBg/dim/label/value/good/bad/
|
|
30
|
-
// warn/empty/selectBg) comes from DEFAULT_PANEL_PALETTE.
|
|
31
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
32
|
-
rejected: '#f97316', // rejected-outcome badge — distinct from success/error
|
|
33
|
-
taskColor: '#22d3ee', // task-target tag
|
|
34
|
-
agentColor: '#a78bfa', // agent-target tag
|
|
35
|
-
} as const);
|
|
36
|
-
|
|
37
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
function fmtTime(ts: number): string {
|
|
40
|
-
const d = new Date(ts);
|
|
41
|
-
const hh = String(d.getHours()).padStart(2, '0');
|
|
42
|
-
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
43
|
-
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
44
|
-
return `${hh}:${mm}:${ss}`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function outcomeColor(outcome: OpsAuditEntry['outcome']): string {
|
|
48
|
-
switch (outcome) {
|
|
49
|
-
case 'success': return C.good;
|
|
50
|
-
case 'rejected': return C.rejected;
|
|
51
|
-
case 'error': return C.bad;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function outcomeLabel(outcome: OpsAuditEntry['outcome']): string {
|
|
56
|
-
switch (outcome) {
|
|
57
|
-
case 'success': return 'OK ';
|
|
58
|
-
case 'rejected': return 'REJECT';
|
|
59
|
-
case 'error': return 'ERR ';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function targetColor(kind: OpsAuditEntry['targetKind']): string {
|
|
64
|
-
return kind === 'task' ? C.taskColor : C.agentColor;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ── Operator intervention actions ───────────────────────────────────────────
|
|
68
|
-
// Target-entry mode: c/p/u/y act on the currently *selected* audit row's
|
|
69
|
-
// target (task or agent) rather than requiring a typed id, mirroring the
|
|
70
|
-
// `/ops task <action> <id>` / `/ops agent cancel <id>` slash commands.
|
|
71
|
-
|
|
72
|
-
type OpsAction = 'cancel' | 'pause' | 'resume' | 'retry';
|
|
73
|
-
|
|
74
|
-
interface OpsActionSubject {
|
|
75
|
-
readonly action: OpsAction;
|
|
76
|
-
readonly targetKind: OpsAuditEntry['targetKind'];
|
|
77
|
-
readonly targetId: string;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const ACTION_VERB: Record<OpsAction, string> = {
|
|
81
|
-
cancel: 'Cancel',
|
|
82
|
-
pause: 'Pause',
|
|
83
|
-
resume: 'Resume',
|
|
84
|
-
retry: 'Retry',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// ── OpsControlPanel ──────────────────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
90
|
-
private readonly _opsPanel: OpsPanel;
|
|
91
|
-
private readonly _opsApi: OpsApi | undefined;
|
|
92
|
-
private _unsub: (() => void) | null = null;
|
|
93
|
-
private _confirm: ConfirmState<OpsActionSubject> | null = null;
|
|
94
|
-
|
|
95
|
-
public constructor(eventFeed: UiEventFeed<OpsEvent>, opsApi?: OpsApi) {
|
|
96
|
-
super('ops-control', 'Ops Control', '◓', 'runtime-ops');
|
|
97
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
98
|
-
this.filterEnabled = true;
|
|
99
|
-
this.filterLabel = 'Filter audit';
|
|
100
|
-
this._opsPanel = new OpsPanel(eventFeed);
|
|
101
|
-
this._opsApi = opsApi;
|
|
102
|
-
this._unsub = this._opsPanel.subscribe(() => this.markDirty());
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
public override onActivate(): void {
|
|
106
|
-
super.onActivate();
|
|
107
|
-
this.selectedIndex = 0;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
public override onDestroy(): void {
|
|
111
|
-
if (this._unsub) {
|
|
112
|
-
this._unsub();
|
|
113
|
-
this._unsub = null;
|
|
114
|
-
}
|
|
115
|
-
this._opsPanel.dispose();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
protected override getPalette(): PanelPalette {
|
|
119
|
-
return C;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
protected getItems(): readonly OpsAuditEntry[] {
|
|
123
|
-
// Return reversed so newest entries appear at top
|
|
124
|
-
return [...this._opsPanel.getSnapshot()].reverse();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
protected override filterMatches(entry: OpsAuditEntry, q: string): boolean {
|
|
128
|
-
return entry.action.toLowerCase().includes(q)
|
|
129
|
-
|| entry.targetKind.toLowerCase().includes(q)
|
|
130
|
-
|| entry.targetId.toLowerCase().includes(q)
|
|
131
|
-
|| entry.outcome.toLowerCase().includes(q);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// -------------------------------------------------------------------------
|
|
135
|
-
// Input — target-entry mode dispatch (acts on the selected audit row)
|
|
136
|
-
// -------------------------------------------------------------------------
|
|
137
|
-
|
|
138
|
-
public override handleInput(key: string): boolean {
|
|
139
|
-
if (this.lastError !== null) this.clearError();
|
|
140
|
-
|
|
141
|
-
if (this._confirm) {
|
|
142
|
-
const result = handleConfirmInput(this._confirm, key);
|
|
143
|
-
if (result === 'confirmed') {
|
|
144
|
-
this._executeConfirmed(this._confirm.subject);
|
|
145
|
-
this._confirm = null;
|
|
146
|
-
this.markDirty();
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
if (result === 'cancelled') {
|
|
150
|
-
this._confirm = null;
|
|
151
|
-
this.markDirty();
|
|
152
|
-
}
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!this.filterActive) {
|
|
157
|
-
switch (key) {
|
|
158
|
-
case 'c': this._requestAction('cancel'); return true;
|
|
159
|
-
case 'p': this._requestAction('pause'); return true;
|
|
160
|
-
case 'u': this._requestAction('resume'); return true;
|
|
161
|
-
case 'y': this._requestAction('retry'); return true;
|
|
162
|
-
default: break;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return super.handleInput(key);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
private _requestAction(action: OpsAction): void {
|
|
170
|
-
const selected = this.getSelectedItem();
|
|
171
|
-
if (!selected) return;
|
|
172
|
-
if (action !== 'cancel' && selected.targetKind === 'agent') {
|
|
173
|
-
this.setError(`Only cancel is supported for agent targets ("${action}" is task-only).`);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
this._confirm = {
|
|
177
|
-
subject: { action, targetKind: selected.targetKind, targetId: selected.targetId },
|
|
178
|
-
label: `${selected.targetKind} ${selected.targetId}`,
|
|
179
|
-
verb: ACTION_VERB[action],
|
|
180
|
-
};
|
|
181
|
-
this.markDirty();
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private _executeConfirmed(subject: OpsActionSubject): void {
|
|
185
|
-
if (!this._opsApi) {
|
|
186
|
-
this.setError('Ops API is not wired for this runtime.');
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
if (subject.targetKind === 'agent') {
|
|
191
|
-
this._opsApi.agents.cancel(subject.targetId);
|
|
192
|
-
} else {
|
|
193
|
-
switch (subject.action) {
|
|
194
|
-
case 'cancel': this._opsApi.tasks.cancel(subject.targetId); break;
|
|
195
|
-
case 'pause': this._opsApi.tasks.pause(subject.targetId); break;
|
|
196
|
-
case 'resume': this._opsApi.tasks.resume(subject.targetId); break;
|
|
197
|
-
case 'retry': this._opsApi.tasks.retry(subject.targetId); break;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
} catch (err) {
|
|
201
|
-
// Dispatched actions re-appear as OPS_AUDIT rows regardless of legality
|
|
202
|
-
// (OpsControlPlane audits rejections too); this surfaces the rare case
|
|
203
|
-
// where the target vanished before the audit event was emitted.
|
|
204
|
-
this.setError(summarizeError(err));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** Live posture: tasks currently eligible for intervention, independent of the audit log. */
|
|
209
|
-
private _postureCounts(): { running: number; blocked: number; retryable: number } {
|
|
210
|
-
const tasks = this._opsApi?.tasks.snapshot().tasks ?? [];
|
|
211
|
-
let running = 0;
|
|
212
|
-
let blocked = 0;
|
|
213
|
-
let retryable = 0;
|
|
214
|
-
for (const task of tasks) {
|
|
215
|
-
if (task.status === 'running') running++;
|
|
216
|
-
else if (task.status === 'blocked') blocked++;
|
|
217
|
-
else if (task.status === 'failed' || task.status === 'cancelled') retryable++;
|
|
218
|
-
}
|
|
219
|
-
return { running, blocked, retryable };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
protected renderItem(entry: OpsAuditEntry, _index: number, _selected: boolean, width: number): Line {
|
|
223
|
-
const seqStr = String(entry.seq).padStart(4, ' ');
|
|
224
|
-
const timeStr = fmtTime(entry.ts);
|
|
225
|
-
const action = fitDisplay(entry.action, 15);
|
|
226
|
-
const kindTag = entry.targetKind === 'task' ? 'T:' : 'A:';
|
|
227
|
-
// Last 10 chars of the id keep the unique suffix; fitDisplay caps display width.
|
|
228
|
-
const shortId = entry.targetId.slice(-10);
|
|
229
|
-
const target = fitDisplay(kindTag + shortId, 14);
|
|
230
|
-
const outLabel = outcomeLabel(entry.outcome);
|
|
231
|
-
const noteRaw = truncateDisplay(entry.note ?? entry.errorMessage ?? '', Math.max(0, width - 63));
|
|
232
|
-
|
|
233
|
-
const segs: Array<[string, string, string?]> = [
|
|
234
|
-
[` ${seqStr} `, C.dim],
|
|
235
|
-
[`${timeStr} `, C.dim],
|
|
236
|
-
[`${action} `, C.value],
|
|
237
|
-
[`${target} `, targetColor(entry.targetKind)],
|
|
238
|
-
[outLabel, outcomeColor(entry.outcome)],
|
|
239
|
-
];
|
|
240
|
-
if (noteRaw) segs.push([` ${noteRaw}`, C.warn]);
|
|
241
|
-
return buildPanelLine(width, segs);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
protected override getEmptyStateMessage(): string {
|
|
245
|
-
return ' No operator interventions recorded.';
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
249
|
-
// No signpost — the live posture row in the header (running/blocked/retryable
|
|
250
|
-
// task counts) already tells the operator whether there is anything to act on.
|
|
251
|
-
return [];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
public render(width: number, height: number): Line[] {
|
|
255
|
-
const entries = this.getVisibleItems();
|
|
256
|
-
this.clampSelection();
|
|
257
|
-
|
|
258
|
-
// Outcome tallies surface posture at a glance (most important runtime info first).
|
|
259
|
-
let ok = 0;
|
|
260
|
-
let rejected = 0;
|
|
261
|
-
let errored = 0;
|
|
262
|
-
for (const e of entries) {
|
|
263
|
-
if (e.outcome === 'success') ok++;
|
|
264
|
-
else if (e.outcome === 'rejected') rejected++;
|
|
265
|
-
else errored++;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const posture = this._postureCounts();
|
|
269
|
-
|
|
270
|
-
const headerLines: Line[] = [
|
|
271
|
-
buildKeyValueLine(width, [
|
|
272
|
-
{ label: 'logged', value: String(entries.length), valueColor: entries.length > 0 ? C.value : C.dim },
|
|
273
|
-
{ label: 'ok', value: String(ok), valueColor: ok > 0 ? C.good : C.dim },
|
|
274
|
-
{ label: 'rejected', value: String(rejected), valueColor: rejected > 0 ? C.rejected : C.dim },
|
|
275
|
-
{ label: 'errors', value: String(errored), valueColor: errored > 0 ? C.bad : C.dim },
|
|
276
|
-
], C),
|
|
277
|
-
buildKeyValueLine(width, [
|
|
278
|
-
{ label: 'running', value: String(posture.running), valueColor: posture.running > 0 ? C.warn : C.dim },
|
|
279
|
-
{ label: 'blocked', value: String(posture.blocked), valueColor: posture.blocked > 0 ? C.warn : C.dim },
|
|
280
|
-
{ label: 'retryable', value: String(posture.retryable), valueColor: posture.retryable > 0 ? C.info : C.dim },
|
|
281
|
-
], C),
|
|
282
|
-
buildPanelLine(width, [[' SEQ TIME ACTION TARGET OUT NOTE', C.label]]),
|
|
283
|
-
];
|
|
284
|
-
|
|
285
|
-
const selected = this.getSelectedItem();
|
|
286
|
-
const footerLines: Line[] = [];
|
|
287
|
-
if (selected) {
|
|
288
|
-
const detail = selected.note ?? selected.errorMessage ?? '(no note)';
|
|
289
|
-
footerLines.push(
|
|
290
|
-
buildPanelLine(width, [
|
|
291
|
-
[' #', C.label],
|
|
292
|
-
[String(selected.seq), C.value],
|
|
293
|
-
[' ', C.dim],
|
|
294
|
-
[outcomeLabel(selected.outcome).trim(), outcomeColor(selected.outcome)],
|
|
295
|
-
[' ', C.dim],
|
|
296
|
-
[selected.action, C.value],
|
|
297
|
-
[' ', C.dim],
|
|
298
|
-
[`${selected.targetKind}:${selected.targetId}`, targetColor(selected.targetKind)],
|
|
299
|
-
]),
|
|
300
|
-
buildPanelLine(width, [
|
|
301
|
-
[' ', C.label],
|
|
302
|
-
[truncateDisplay(detail, Math.max(0, width - 4)), C.warn],
|
|
303
|
-
]),
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
if (this._confirm) {
|
|
307
|
-
footerLines.push(...renderConfirmLines(width, this._confirm));
|
|
308
|
-
}
|
|
309
|
-
footerLines.push(
|
|
310
|
-
this.filterActive
|
|
311
|
-
? buildKeyboardHints(width, [
|
|
312
|
-
{ keys: 'type', label: 'filter audit' },
|
|
313
|
-
{ keys: 'Enter', label: 'apply' },
|
|
314
|
-
{ keys: 'Esc', label: 'clear' },
|
|
315
|
-
], C)
|
|
316
|
-
: buildKeyboardHints(width, [
|
|
317
|
-
{ keys: 'Up/Down', label: 'browse log' },
|
|
318
|
-
{ keys: 'c/p/u/y', label: 'cancel/pause/resume/retry selected' },
|
|
319
|
-
{ keys: '/', label: 'filter' },
|
|
320
|
-
], C),
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
return this.renderList(width, height, {
|
|
324
|
-
title: 'Operator Control Plane',
|
|
325
|
-
header: headerLines,
|
|
326
|
-
footer: footerLines,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
}
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ops Strategy Timeline Panel.
|
|
3
|
-
*
|
|
4
|
-
* Renders the Adaptive Execution Planner state: current strategy, reason
|
|
5
|
-
* code, mode, override status, and a scrollable history of past decisions.
|
|
6
|
-
*
|
|
7
|
-
* Registered as panel id 'ops' in builtin-panels.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { BasePanel } from './base-panel.ts';
|
|
11
|
-
import type { Line } from '../types/grid.ts';
|
|
12
|
-
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
13
|
-
import type { PlannerDecision, ExecutionStrategy } from '@pellux/goodvibes-sdk/platform/core';
|
|
14
|
-
import type { PlannerEvent, PlanRuntimeService } from '@/runtime/index.ts';
|
|
15
|
-
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
16
|
-
import type { OpsStrategyQuery } from '../runtime/ui-service-queries.ts';
|
|
17
|
-
import {
|
|
18
|
-
buildAlignedRow,
|
|
19
|
-
buildEmptyState,
|
|
20
|
-
buildKeyboardHints,
|
|
21
|
-
buildPanelLine,
|
|
22
|
-
buildStyledPanelLine,
|
|
23
|
-
buildPanelWorkspace,
|
|
24
|
-
extendPalette,
|
|
25
|
-
resolveScrollablePanelSection,
|
|
26
|
-
DEFAULT_PANEL_PALETTE,
|
|
27
|
-
} from './polish.ts';
|
|
28
|
-
|
|
29
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
30
|
-
auto: '#00cccc',
|
|
31
|
-
single: '#00cc66',
|
|
32
|
-
cohort: '#cccc00',
|
|
33
|
-
background: '#cc66cc',
|
|
34
|
-
remote: '#cccccc',
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const STRATEGY_FG: Record<ExecutionStrategy, string> = {
|
|
38
|
-
auto: C.auto,
|
|
39
|
-
single: C.single,
|
|
40
|
-
cohort: C.cohort,
|
|
41
|
-
background: C.background,
|
|
42
|
-
remote: C.remote,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const STRATEGY_ICON: Record<ExecutionStrategy, string> = {
|
|
46
|
-
auto: '~',
|
|
47
|
-
single: '▸',
|
|
48
|
-
cohort: '◆',
|
|
49
|
-
background: '.',
|
|
50
|
-
remote: '▸',
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Cycle order for the o (override) and m (mode) keys — mirrors /plan's
|
|
54
|
-
// documented strategy list (planner-command-handler.ts VALID_STRATEGIES).
|
|
55
|
-
const STRATEGY_CYCLE: readonly ExecutionStrategy[] = ['auto', 'single', 'cohort', 'background', 'remote'];
|
|
56
|
-
|
|
57
|
-
function nextStrategy(current: ExecutionStrategy): ExecutionStrategy {
|
|
58
|
-
const idx = STRATEGY_CYCLE.indexOf(current);
|
|
59
|
-
return STRATEGY_CYCLE[(idx + 1) % STRATEGY_CYCLE.length]!;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// OpsStrategyPanel
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
export class OpsStrategyPanel extends BasePanel {
|
|
67
|
-
private unsubscribers: Array<() => void> = [];
|
|
68
|
-
private scrollOffset = 0;
|
|
69
|
-
private history: PlannerDecision[] = [];
|
|
70
|
-
private readonly adaptivePlanner: OpsStrategyQuery;
|
|
71
|
-
private readonly planRuntime: PlanRuntimeService | undefined;
|
|
72
|
-
|
|
73
|
-
constructor(
|
|
74
|
-
private readonly plannerEvents: UiEventFeed<PlannerEvent>,
|
|
75
|
-
adaptivePlanner: OpsStrategyQuery,
|
|
76
|
-
planRuntime?: PlanRuntimeService,
|
|
77
|
-
) {
|
|
78
|
-
super('ops', 'Ops', '◫', 'runtime-ops');
|
|
79
|
-
this.adaptivePlanner = adaptivePlanner;
|
|
80
|
-
this.planRuntime = planRuntime;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
override onActivate(): void {
|
|
84
|
-
super.onActivate();
|
|
85
|
-
this._syncHistory();
|
|
86
|
-
this.unsubscribers.push(
|
|
87
|
-
this.plannerEvents.on('PLAN_STRATEGY_SELECTED', () => {
|
|
88
|
-
this._syncHistory();
|
|
89
|
-
this.markDirty();
|
|
90
|
-
}),
|
|
91
|
-
this.plannerEvents.on('PLAN_STRATEGY_OVERRIDDEN', () => {
|
|
92
|
-
this._syncHistory();
|
|
93
|
-
this.markDirty();
|
|
94
|
-
}),
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
override onDestroy(): void {
|
|
99
|
-
for (const unsub of this.unsubscribers) unsub();
|
|
100
|
-
this.unsubscribers = [];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
handleInput(key: string): boolean {
|
|
104
|
-
if (this.lastError !== null) this.clearError();
|
|
105
|
-
|
|
106
|
-
if (key === 'up' || key === 'k') {
|
|
107
|
-
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
108
|
-
this.markDirty();
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
if (key === 'down' || key === 'j') {
|
|
112
|
-
this.scrollOffset++;
|
|
113
|
-
this.markDirty();
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
if (key === 'g') {
|
|
117
|
-
this.scrollOffset = 0;
|
|
118
|
-
this.markDirty();
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
if (key === 'G') {
|
|
122
|
-
this.scrollOffset = Math.max(0, this.history.length - 1);
|
|
123
|
-
this.markDirty();
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
if (key === 'o') {
|
|
127
|
-
this._cycleOverride();
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
if (key === 'c') {
|
|
131
|
-
this._clearOverride();
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
if (key === 'm') {
|
|
135
|
-
this._cycleMode();
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// -------------------------------------------------------------------------
|
|
142
|
-
// Override verbs — o=cycle override, c=clear, m=cycle mode, all dispatched
|
|
143
|
-
// through the same /plan subcommand bridge the slash command uses
|
|
144
|
-
// (planning-runtime.ts), so panel and slash-command behavior stay identical.
|
|
145
|
-
// -------------------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
private _cycleOverride(): void {
|
|
148
|
-
if (!this.planRuntime) {
|
|
149
|
-
this.setError('Plan runtime is not wired for this runtime.');
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const current = this.adaptivePlanner.getOverride() ?? this.adaptivePlanner.getMode();
|
|
153
|
-
const result = this.planRuntime('override', [nextStrategy(current)]);
|
|
154
|
-
if (!result.ok) this.setError(result.output);
|
|
155
|
-
this.markDirty();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private _clearOverride(): void {
|
|
159
|
-
if (!this.planRuntime) {
|
|
160
|
-
this.setError('Plan runtime is not wired for this runtime.');
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const result = this.planRuntime('clear', []);
|
|
164
|
-
if (!result.ok) this.setError(result.output);
|
|
165
|
-
this.markDirty();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
private _cycleMode(): void {
|
|
169
|
-
if (!this.planRuntime) {
|
|
170
|
-
this.setError('Plan runtime is not wired for this runtime.');
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const result = this.planRuntime('mode', [nextStrategy(this.adaptivePlanner.getMode())]);
|
|
174
|
-
if (!result.ok) this.setError(result.output);
|
|
175
|
-
this.markDirty();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
render(width: number, height: number): Line[] {
|
|
179
|
-
const latest = this.adaptivePlanner.getLatest();
|
|
180
|
-
const mode = this.adaptivePlanner.getMode();
|
|
181
|
-
const override = this.adaptivePlanner.getOverride();
|
|
182
|
-
const intro = 'Review adaptive execution planner decisions, overrides, and recent strategy history.';
|
|
183
|
-
const errorLine = this.renderErrorLine(width);
|
|
184
|
-
const footerLines = [
|
|
185
|
-
...(errorLine ? [errorLine] : []),
|
|
186
|
-
buildKeyboardHints(width, [
|
|
187
|
-
{ keys: 'Up/Down', label: 'scroll history' },
|
|
188
|
-
{ keys: 'g/G', label: 'top/bottom' },
|
|
189
|
-
{ keys: 'o', label: 'cycle override' },
|
|
190
|
-
{ keys: 'c', label: 'clear override' },
|
|
191
|
-
{ keys: 'm', label: 'cycle mode' },
|
|
192
|
-
], C),
|
|
193
|
-
];
|
|
194
|
-
const statusLines: Line[] = [
|
|
195
|
-
buildPanelLine(width, [
|
|
196
|
-
[' Mode ', C.label],
|
|
197
|
-
[mode.toUpperCase(), C.value],
|
|
198
|
-
[' Override ', C.label],
|
|
199
|
-
[override ? `${override.toUpperCase()} [ACTIVE]` : 'none', override ? C.warn : C.dim],
|
|
200
|
-
[' Decisions ', C.label],
|
|
201
|
-
[String(this.history.length), this.history.length > 0 ? C.info : C.dim],
|
|
202
|
-
]),
|
|
203
|
-
];
|
|
204
|
-
|
|
205
|
-
if (latest) {
|
|
206
|
-
statusLines.push(buildPanelLine(width, [
|
|
207
|
-
[' Last ', C.label],
|
|
208
|
-
[`${STRATEGY_ICON[latest.selected]} ${latest.selected.toUpperCase()}`, STRATEGY_FG[latest.selected]],
|
|
209
|
-
[' Reason ', C.label],
|
|
210
|
-
[latest.reasonCode, C.dim],
|
|
211
|
-
]));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (this.history.length === 0) {
|
|
215
|
-
return buildPanelWorkspace(width, height, {
|
|
216
|
-
title: ' Ops Strategy',
|
|
217
|
-
intro,
|
|
218
|
-
sections: [
|
|
219
|
-
{ title: 'Status', lines: statusLines },
|
|
220
|
-
{
|
|
221
|
-
lines: buildEmptyState(
|
|
222
|
-
width,
|
|
223
|
-
' No decisions recorded yet',
|
|
224
|
-
'Adaptive planner decisions appear here once the planner begins selecting strategies. Seed or inspect an execution plan to drive runtime activity.',
|
|
225
|
-
[{ command: '/plan', summary: 'seed or inspect an execution plan; strategy decisions start appearing here once it runs' }],
|
|
226
|
-
C,
|
|
227
|
-
),
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
footerLines,
|
|
231
|
-
palette: C,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const historyLines = this._renderHistory(width);
|
|
236
|
-
const statusSection = { title: 'Status', lines: statusLines } as const;
|
|
237
|
-
const historySection = resolveScrollablePanelSection(width, height, {
|
|
238
|
-
intro,
|
|
239
|
-
footerLines,
|
|
240
|
-
palette: C,
|
|
241
|
-
beforeSections: [statusSection],
|
|
242
|
-
section: {
|
|
243
|
-
title: `Decision History (${this.history.length})`,
|
|
244
|
-
scrollableLines: historyLines,
|
|
245
|
-
scrollOffset: Math.min(this.scrollOffset, Math.max(0, historyLines.length - 1)),
|
|
246
|
-
minRows: 8,
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
this.scrollOffset = historySection.scrollOffset;
|
|
250
|
-
|
|
251
|
-
return buildPanelWorkspace(width, height, {
|
|
252
|
-
title: ' Ops Strategy',
|
|
253
|
-
intro,
|
|
254
|
-
sections: [
|
|
255
|
-
statusSection,
|
|
256
|
-
historySection.section,
|
|
257
|
-
],
|
|
258
|
-
footerLines,
|
|
259
|
-
palette: C,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// -------------------------------------------------------------------------
|
|
264
|
-
// Private helpers
|
|
265
|
-
// -------------------------------------------------------------------------
|
|
266
|
-
|
|
267
|
-
private _syncHistory(): void {
|
|
268
|
-
this.history = this.adaptivePlanner.getHistory(50);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
private _renderHistory(width: number): Line[] {
|
|
272
|
-
if (this.history.length === 0) {
|
|
273
|
-
return [buildStyledPanelLine(width, [{ text: ' No history yet.', fg: C.dim, dim: true }])];
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const lines: Line[] = [];
|
|
277
|
-
|
|
278
|
-
const reversed = [...this.history].reverse();
|
|
279
|
-
for (let i = 0; i < reversed.length; i++) {
|
|
280
|
-
const d = reversed[i]!;
|
|
281
|
-
const ts = new Date(d.timestamp).toLocaleTimeString();
|
|
282
|
-
const fg = STRATEGY_FG[d.selected];
|
|
283
|
-
const icon = STRATEGY_ICON[d.selected];
|
|
284
|
-
const num = String(i + 1).padStart(3);
|
|
285
|
-
const overrideMark = d.overrideActive ? ' [O]' : '';
|
|
286
|
-
|
|
287
|
-
// Row 1: index + icon + strategy on the left, timestamp right-aligned.
|
|
288
|
-
// buildAlignedRow keeps the timestamp flush-right even with wide glyphs.
|
|
289
|
-
const tsCol = getDisplayWidth(`${ts} `);
|
|
290
|
-
lines.push(buildAlignedRow(
|
|
291
|
-
width,
|
|
292
|
-
[
|
|
293
|
-
{ text: ` ${num}. ${icon} ${d.selected.toUpperCase()}${overrideMark}`, fg, bold: true },
|
|
294
|
-
{ text: `${ts} `, fg: C.dim, dim: true },
|
|
295
|
-
],
|
|
296
|
-
[
|
|
297
|
-
{ width: Math.max(0, width - tsCol - 1) },
|
|
298
|
-
{ width: tsCol, align: 'right' },
|
|
299
|
-
],
|
|
300
|
-
{ gap: 1 },
|
|
301
|
-
));
|
|
302
|
-
|
|
303
|
-
// Row 2: reason code
|
|
304
|
-
lines.push(buildStyledPanelLine(width, [{ text: ` ${d.reasonCode}`, fg: C.dim, dim: true }]));
|
|
305
|
-
|
|
306
|
-
// Row 3+: top-2 scored candidates (auto mode only)
|
|
307
|
-
if (!d.overrideActive && d.candidates.length > 1) {
|
|
308
|
-
const top2 = d.candidates.slice(0, 2);
|
|
309
|
-
for (const c of top2) {
|
|
310
|
-
lines.push(buildStyledPanelLine(width, [
|
|
311
|
-
{ text: ' ', fg: C.dim, dim: true },
|
|
312
|
-
{ text: c.strategy.padEnd(12), fg: STRATEGY_FG[c.strategy] },
|
|
313
|
-
{ text: ` score ${c.score}`, fg: C.dim, dim: true },
|
|
314
|
-
]));
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return lines;
|
|
320
|
-
}
|
|
321
|
-
}
|