@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,517 +0,0 @@
|
|
|
1
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
2
|
-
import type { Line } from '../types/grid.ts';
|
|
3
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
4
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
5
|
-
import { BasePanel } from './base-panel.ts';
|
|
6
|
-
import type {
|
|
7
|
-
PolicyRuntimeState,
|
|
8
|
-
PolicyBundleVersion,
|
|
9
|
-
PolicyDiffResult,
|
|
10
|
-
DivergenceDashboardSnapshot,
|
|
11
|
-
DivergenceStats,
|
|
12
|
-
PermissionAuditEntry,
|
|
13
|
-
PolicyLintFinding,
|
|
14
|
-
PolicySimulationSummary,
|
|
15
|
-
PolicyPreflightReview,
|
|
16
|
-
} from '@/runtime/index.ts';
|
|
17
|
-
import {
|
|
18
|
-
buildEmptyState,
|
|
19
|
-
buildKeyboardHints,
|
|
20
|
-
buildKeyValueLine,
|
|
21
|
-
buildPanelLine,
|
|
22
|
-
buildPanelWorkspace,
|
|
23
|
-
resolveScrollablePanelSection,
|
|
24
|
-
DEFAULT_PANEL_PALETTE,
|
|
25
|
-
} from './polish.ts';
|
|
26
|
-
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
27
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
28
|
-
|
|
29
|
-
// Base chrome only — no domain accents needed; the title band, state colors,
|
|
30
|
-
// and text tokens all come straight from DEFAULT_PANEL_PALETTE (WO-002).
|
|
31
|
-
const C = DEFAULT_PANEL_PALETTE;
|
|
32
|
-
|
|
33
|
-
function fmtTime(value: string | undefined): string {
|
|
34
|
-
if (!value) return 'n/a';
|
|
35
|
-
return value.replace('T', ' ').slice(0, 19);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function fmtRate(value: number | undefined): string {
|
|
39
|
-
if (value === undefined) return 'n/a';
|
|
40
|
-
return `${(value * 100).toFixed(1)}%`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function gateColor(status: string | undefined): string {
|
|
44
|
-
switch (status) {
|
|
45
|
-
case 'allowed':
|
|
46
|
-
return C.good;
|
|
47
|
-
case 'blocked':
|
|
48
|
-
return C.bad;
|
|
49
|
-
case 'no_data':
|
|
50
|
-
return C.warn;
|
|
51
|
-
default:
|
|
52
|
-
return C.dim;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* A point-in-time snapshot of policy state for diagnostics rendering.
|
|
58
|
-
*/
|
|
59
|
-
interface PolicyPanelSnapshot {
|
|
60
|
-
/** The currently enforced bundle, or null if no policy is active. */
|
|
61
|
-
current: PolicyBundleVersion | null;
|
|
62
|
-
/** The pending candidate bundle, or null if none loaded. */
|
|
63
|
-
candidate: PolicyBundleVersion | null;
|
|
64
|
-
/** History of previous active bundles (most recent first). */
|
|
65
|
-
history: PolicyBundleVersion[];
|
|
66
|
-
/** Diff between current and candidate, or null if unavailable. */
|
|
67
|
-
diff: PolicyDiffResult | null;
|
|
68
|
-
/** Divergence dashboard snapshot, or null if no panel attached. */
|
|
69
|
-
divergence: DivergenceDashboardSnapshot | null;
|
|
70
|
-
/** Recent permission requests and decisions for operator audit review. */
|
|
71
|
-
recentPermissionAudit: readonly PermissionAuditEntry[];
|
|
72
|
-
/** Policy lint findings for current and candidate bundles. */
|
|
73
|
-
lintFindings: readonly PolicyLintFinding[];
|
|
74
|
-
/** Concrete scenario results from the most recent policy simulation run. */
|
|
75
|
-
lastSimulationSummary: PolicySimulationSummary | null;
|
|
76
|
-
/** Most recent proactive policy preflight review. */
|
|
77
|
-
lastPreflightReview: PolicyPreflightReview | null;
|
|
78
|
-
/** ISO 8601 timestamp of when this snapshot was captured. */
|
|
79
|
-
capturedAt: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Subactions dispatched through `/policy <name>` via `dispatchPolicyCommand`. */
|
|
83
|
-
type PolicyDispatchAction = 'simulate' | 'preflight' | 'lint' | 'promote' | 'rollback';
|
|
84
|
-
|
|
85
|
-
export class PolicyPanel extends BasePanel {
|
|
86
|
-
private readonly _state: PolicyRuntimeState;
|
|
87
|
-
private readonly _unsub: (() => void) | null;
|
|
88
|
-
private _scrollOffset = 0;
|
|
89
|
-
|
|
90
|
-
// Inline confirm for the gate-aware promote/rollback actions.
|
|
91
|
-
private _confirm: ConfirmState<PolicyDispatchAction> | null = null;
|
|
92
|
-
|
|
93
|
-
// Pending dispatch resolved via handlePanelIntegrationAction, which is the
|
|
94
|
-
// only place `executeCommand` is available (mirrors IncidentReviewPanel).
|
|
95
|
-
private _pendingAction: PolicyDispatchAction | null = null;
|
|
96
|
-
|
|
97
|
-
public constructor(state: PolicyRuntimeState) {
|
|
98
|
-
super('policy', 'Policy', '▭', 'security-policy');
|
|
99
|
-
this._state = state;
|
|
100
|
-
this._unsub = state.subscribe(() => this.markDirty());
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public override onActivate(): void {
|
|
104
|
-
super.onActivate();
|
|
105
|
-
this._scrollOffset = 0;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public override onDestroy(): void {
|
|
109
|
-
this._unsub?.();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public handleInput(key: string): boolean {
|
|
113
|
-
if (this._confirm) {
|
|
114
|
-
const outcome = handleConfirmInput(this._confirm, key);
|
|
115
|
-
if (outcome === 'confirmed') {
|
|
116
|
-
this._pendingAction = this._confirm.subject;
|
|
117
|
-
this._confirm = null;
|
|
118
|
-
this.markDirty();
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
if (outcome === 'cancelled') {
|
|
122
|
-
this._confirm = null;
|
|
123
|
-
this.markDirty();
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
return true; // absorbed — keep the confirm dialog pending
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (key === 'up' || key === 'k') {
|
|
130
|
-
this._scrollOffset = Math.max(0, this._scrollOffset - 1);
|
|
131
|
-
this.markDirty();
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
if (key === 'down' || key === 'j') {
|
|
135
|
-
this._scrollOffset++;
|
|
136
|
-
this.markDirty();
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
if (key === 's') {
|
|
140
|
-
this._pendingAction = 'simulate';
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
if (key === 'f') {
|
|
144
|
-
this._pendingAction = 'preflight';
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
if (key === 'l') {
|
|
148
|
-
this._pendingAction = 'lint';
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
if (key === 'p') {
|
|
152
|
-
const snapshot = this._state.getSnapshot();
|
|
153
|
-
if (!snapshot.candidate) return false;
|
|
154
|
-
const gateStatus = snapshot.divergence?.gate.status ?? 'no_data';
|
|
155
|
-
this._confirm = {
|
|
156
|
-
subject: 'promote',
|
|
157
|
-
label: `promote ${snapshot.candidate.bundle.bundleId} (gate: ${gateStatus})`,
|
|
158
|
-
verb: 'Promote',
|
|
159
|
-
};
|
|
160
|
-
this.markDirty();
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
if (key === 'b') {
|
|
164
|
-
const snapshot = this._state.getSnapshot();
|
|
165
|
-
if (!snapshot.current) return false;
|
|
166
|
-
this._confirm = {
|
|
167
|
-
subject: 'rollback',
|
|
168
|
-
label: `rollback active bundle ${snapshot.current.bundle.bundleId}`,
|
|
169
|
-
verb: 'Rollback',
|
|
170
|
-
};
|
|
171
|
-
this.markDirty();
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
if (key === 'r') {
|
|
175
|
-
// Trend-record is only meaningful once a simulation dashboard exists;
|
|
176
|
-
// otherwise there is nothing to sample and the key is a no-op.
|
|
177
|
-
if (!this._state.getDashboard()) return false;
|
|
178
|
-
this._state.recordTrendEntry();
|
|
179
|
-
this.markDirty();
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
186
|
-
if (!this._pendingAction) return false;
|
|
187
|
-
const action = this._pendingAction;
|
|
188
|
-
this._pendingAction = null;
|
|
189
|
-
void ctx.executeCommand?.('policy', [action]).catch((err) => {
|
|
190
|
-
logger.debug('policy panel command dispatch failed', { err, action });
|
|
191
|
-
});
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
public render(width: number, height: number): Line[] {
|
|
196
|
-
this.needsRender = false;
|
|
197
|
-
if (this._confirm) {
|
|
198
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
199
|
-
title: 'Policy And Governance',
|
|
200
|
-
sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this._confirm) }],
|
|
201
|
-
palette: C,
|
|
202
|
-
});
|
|
203
|
-
while (lines.length < height) {
|
|
204
|
-
lines.push(createEmptyLine(width));
|
|
205
|
-
}
|
|
206
|
-
return lines.slice(0, height);
|
|
207
|
-
}
|
|
208
|
-
const snapshot = this._state.getSnapshot();
|
|
209
|
-
const summaryLine = this._buildSummary(width, snapshot);
|
|
210
|
-
const content = this._buildContent(width, snapshot);
|
|
211
|
-
const dashboardActive = snapshot.divergence !== null;
|
|
212
|
-
const hintsLine = buildKeyboardHints(width, [
|
|
213
|
-
{ keys: '↑/↓', label: 'scroll' },
|
|
214
|
-
{ keys: 's', label: 'simulate' },
|
|
215
|
-
{ keys: 'f', label: 'preflight' },
|
|
216
|
-
{ keys: 'l', label: 'lint' },
|
|
217
|
-
{ keys: 'p', label: 'promote' },
|
|
218
|
-
{ keys: 'b', label: 'rollback' },
|
|
219
|
-
...(dashboardActive ? [{ keys: 'r', label: 'record divergence snapshot' }] : []),
|
|
220
|
-
], C);
|
|
221
|
-
const summarySection = { lines: [summaryLine] };
|
|
222
|
-
const governanceSection = resolveScrollablePanelSection(width, height, {
|
|
223
|
-
footerLines: [hintsLine],
|
|
224
|
-
palette: C,
|
|
225
|
-
beforeSections: [summarySection],
|
|
226
|
-
section: {
|
|
227
|
-
title: 'Governance',
|
|
228
|
-
scrollableLines: content,
|
|
229
|
-
scrollOffset: this._scrollOffset,
|
|
230
|
-
minRows: 4,
|
|
231
|
-
appendWindowSummary: content.length > 0 ? { dimColor: C.dim } : undefined,
|
|
232
|
-
},
|
|
233
|
-
});
|
|
234
|
-
this._scrollOffset = governanceSection.scrollOffset;
|
|
235
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
236
|
-
title: 'Policy And Governance',
|
|
237
|
-
sections: [summarySection, governanceSection.section],
|
|
238
|
-
footerLines: [hintsLine],
|
|
239
|
-
palette: C,
|
|
240
|
-
});
|
|
241
|
-
while (lines.length < height) {
|
|
242
|
-
lines.push(createEmptyLine(width));
|
|
243
|
-
}
|
|
244
|
-
return lines.slice(0, height);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/** Top-of-panel posture summary: the highest-signal governance state at a glance. */
|
|
248
|
-
private _buildSummary(width: number, snapshot: PolicyPanelSnapshot): Line {
|
|
249
|
-
const preflight = snapshot.lastPreflightReview;
|
|
250
|
-
const divergence = snapshot.divergence;
|
|
251
|
-
const lintCount = snapshot.lintFindings.length;
|
|
252
|
-
const preflightStatus = preflight ? preflight.status : 'none';
|
|
253
|
-
const preflightColor = !preflight
|
|
254
|
-
? C.dim
|
|
255
|
-
: preflight.status === 'pass'
|
|
256
|
-
? C.good
|
|
257
|
-
: preflight.status === 'warn'
|
|
258
|
-
? C.warn
|
|
259
|
-
: C.bad;
|
|
260
|
-
const gateStatus = divergence?.gate.status ?? 'n/a';
|
|
261
|
-
return buildKeyValueLine(width, [
|
|
262
|
-
{ label: 'bundles', value: `${snapshot.current ? 1 : 0}+${snapshot.candidate ? 1 : 0}c`, valueColor: snapshot.current || snapshot.candidate ? C.value : C.dim },
|
|
263
|
-
{ label: 'preflight', value: preflightStatus.toUpperCase(), valueColor: preflightColor },
|
|
264
|
-
{ label: 'gate', value: gateStatus, valueColor: gateColor(gateStatus) },
|
|
265
|
-
{ label: 'lint', value: String(lintCount), valueColor: lintCount > 0 ? C.warn : C.dim },
|
|
266
|
-
], C);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private _buildContent(width: number, snapshot: PolicyPanelSnapshot): Line[] {
|
|
270
|
-
const lines: Line[] = [];
|
|
271
|
-
const current = snapshot.current;
|
|
272
|
-
const candidate = snapshot.candidate;
|
|
273
|
-
const divergence = snapshot.divergence;
|
|
274
|
-
const permissionAudit = snapshot.recentPermissionAudit;
|
|
275
|
-
const lintFindings = snapshot.lintFindings;
|
|
276
|
-
const simulationSummary = snapshot.lastSimulationSummary;
|
|
277
|
-
const preflightReview = snapshot.lastPreflightReview;
|
|
278
|
-
|
|
279
|
-
const nothingRecorded = !current && !candidate && !divergence
|
|
280
|
-
&& snapshot.history.length === 0 && permissionAudit.length === 0
|
|
281
|
-
&& lintFindings.length === 0 && !simulationSummary && !preflightReview;
|
|
282
|
-
if (nothingRecorded) {
|
|
283
|
-
// No divergence dashboard exists in this branch by construction
|
|
284
|
-
// (nothingRecorded requires !divergence), so the gate is honestly 'n/a'.
|
|
285
|
-
lines.push(...buildEmptyState(
|
|
286
|
-
width,
|
|
287
|
-
' No policy bundles loaded.',
|
|
288
|
-
'Bundle: none active, none candidate. Gate: n/a.',
|
|
289
|
-
[
|
|
290
|
-
{ command: '/policy load', summary: 'load a policy bundle to begin governance review' },
|
|
291
|
-
],
|
|
292
|
-
C,
|
|
293
|
-
));
|
|
294
|
-
return lines;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (current) {
|
|
298
|
-
lines.push(buildPanelLine(width, [[' Current', C.label]]));
|
|
299
|
-
lines.push(buildPanelLine(width, [
|
|
300
|
-
[' Bundle: ', C.label],
|
|
301
|
-
[current.bundle.bundleId, C.value],
|
|
302
|
-
[' State: ', C.label],
|
|
303
|
-
[current.state, C.info],
|
|
304
|
-
]));
|
|
305
|
-
lines.push(buildPanelLine(width, [
|
|
306
|
-
[' Loaded: ', C.label],
|
|
307
|
-
[fmtTime(current.loadedAt), C.dim],
|
|
308
|
-
[' Active: ', C.label],
|
|
309
|
-
[fmtTime(current.activatedAt), C.dim],
|
|
310
|
-
]));
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (candidate) {
|
|
314
|
-
lines.push(buildPanelLine(width, [[' Candidate', C.label]]));
|
|
315
|
-
lines.push(buildPanelLine(width, [
|
|
316
|
-
[' Bundle: ', C.label],
|
|
317
|
-
[candidate.bundle.bundleId, C.value],
|
|
318
|
-
[' State: ', C.label],
|
|
319
|
-
[candidate.state, C.info],
|
|
320
|
-
]));
|
|
321
|
-
lines.push(buildPanelLine(width, [
|
|
322
|
-
[' Loaded: ', C.label],
|
|
323
|
-
[fmtTime(candidate.loadedAt), C.dim],
|
|
324
|
-
[' Rules: ', C.label],
|
|
325
|
-
[String(candidate.rules.length), C.value],
|
|
326
|
-
]));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (snapshot.diff) {
|
|
330
|
-
const diff = snapshot.diff;
|
|
331
|
-
lines.push(buildPanelLine(width, [[' Diff', C.label]]));
|
|
332
|
-
lines.push(buildPanelLine(width, [
|
|
333
|
-
[' ', C.label],
|
|
334
|
-
[`${diff.fromBundleId} -> ${diff.toBundleId}`, C.value],
|
|
335
|
-
[' Changes: ', C.label],
|
|
336
|
-
[String(diff.totalChanges), C.value],
|
|
337
|
-
]));
|
|
338
|
-
if (diff.added.length > 0) {
|
|
339
|
-
lines.push(buildPanelLine(width, [[' Added', C.good]]));
|
|
340
|
-
for (const rule of diff.added.slice(0, 5)) {
|
|
341
|
-
lines.push(buildPanelLine(width, [
|
|
342
|
-
[' + ', C.good],
|
|
343
|
-
[truncateDisplay(`${rule.id} (${rule.type}, effect=${rule.effect})`, Math.max(0, width - 6)), C.value],
|
|
344
|
-
]));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
if (diff.removed.length > 0) {
|
|
348
|
-
lines.push(buildPanelLine(width, [[' Removed', C.bad]]));
|
|
349
|
-
for (const rule of diff.removed.slice(0, 5)) {
|
|
350
|
-
lines.push(buildPanelLine(width, [
|
|
351
|
-
[' - ', C.bad],
|
|
352
|
-
[truncateDisplay(`${rule.id} (${rule.type}, effect=${rule.effect})`, Math.max(0, width - 6)), C.value],
|
|
353
|
-
]));
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (diff.changed.length > 0) {
|
|
357
|
-
lines.push(buildPanelLine(width, [[' Changed', C.warn]]));
|
|
358
|
-
for (const change of diff.changed.slice(0, 5)) {
|
|
359
|
-
lines.push(buildPanelLine(width, [
|
|
360
|
-
[' ~ ', C.warn],
|
|
361
|
-
[truncateDisplay(change.ruleId, Math.max(0, width - 6)), C.value],
|
|
362
|
-
]));
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (divergence) {
|
|
368
|
-
lines.push(buildPanelLine(width, [[' Governance Gate', C.label]]));
|
|
369
|
-
lines.push(buildPanelLine(width, [
|
|
370
|
-
[' Mode: ', C.label],
|
|
371
|
-
[divergence.mode, C.info],
|
|
372
|
-
[' Gate: ', C.label],
|
|
373
|
-
[divergence.gate.status, gateColor(divergence.gate.status)],
|
|
374
|
-
[' Divergence: ', C.label],
|
|
375
|
-
[fmtRate(divergence.gate.divergenceRate ?? divergence.report.overall.divergenceRate), C.value],
|
|
376
|
-
]));
|
|
377
|
-
lines.push(buildPanelLine(width, [
|
|
378
|
-
[' Evaluations: ', C.label],
|
|
379
|
-
[String(divergence.report.overall.totalEvaluations), C.value],
|
|
380
|
-
[' Trend points: ', C.label],
|
|
381
|
-
[String(divergence.trend.length), C.value],
|
|
382
|
-
]));
|
|
383
|
-
const prefixEntries = Object.entries(divergence.report.byCommandPrefix);
|
|
384
|
-
if (prefixEntries.length > 0) {
|
|
385
|
-
lines.push(buildPanelLine(width, [[' Divergence by command prefix', C.label]]));
|
|
386
|
-
for (const [prefix, stats] of prefixEntries.slice(0, 5)) {
|
|
387
|
-
lines.push(buildPanelLine(width, [
|
|
388
|
-
[' ', C.label],
|
|
389
|
-
[truncateDisplay(prefix, Math.max(0, Math.floor(width * 0.4))), C.value],
|
|
390
|
-
[' ', C.label],
|
|
391
|
-
[fmtRate(stats.divergenceRate), stats.divergenceRate > 0 ? C.warn : C.good],
|
|
392
|
-
[' ', C.label],
|
|
393
|
-
[`${stats.total}/${stats.totalEvaluations}`, C.dim],
|
|
394
|
-
]));
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
const classEntries = Object.entries(divergence.report.byToolClass) as Array<[string, DivergenceStats | undefined]>;
|
|
398
|
-
const populatedClassEntries = classEntries.filter((entry): entry is [string, DivergenceStats] => entry[1] !== undefined);
|
|
399
|
-
if (populatedClassEntries.length > 0) {
|
|
400
|
-
lines.push(buildPanelLine(width, [[' Divergence by tool class', C.label]]));
|
|
401
|
-
for (const [cls, stats] of populatedClassEntries.slice(0, 5)) {
|
|
402
|
-
lines.push(buildPanelLine(width, [
|
|
403
|
-
[' ', C.label],
|
|
404
|
-
[truncateDisplay(cls, Math.max(0, Math.floor(width * 0.4))), C.value],
|
|
405
|
-
[' ', C.label],
|
|
406
|
-
[fmtRate(stats.divergenceRate), stats.divergenceRate > 0 ? C.warn : C.good],
|
|
407
|
-
[' ', C.label],
|
|
408
|
-
[`${stats.total}/${stats.totalEvaluations}`, C.dim],
|
|
409
|
-
]));
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (snapshot.history.length > 0) {
|
|
415
|
-
lines.push(buildPanelLine(width, [[' History', C.label]]));
|
|
416
|
-
for (const version of snapshot.history.slice(0, 5)) {
|
|
417
|
-
lines.push(buildPanelLine(width, [
|
|
418
|
-
[' ', C.label],
|
|
419
|
-
[version.bundle.bundleId, C.value],
|
|
420
|
-
[' ', C.label],
|
|
421
|
-
[version.state, C.dim],
|
|
422
|
-
[' ', C.label],
|
|
423
|
-
[fmtTime(version.activatedAt ?? version.loadedAt), C.dim],
|
|
424
|
-
]));
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (permissionAudit.length > 0) {
|
|
429
|
-
lines.push(buildPanelLine(width, [[' Permission Audit', C.label]]));
|
|
430
|
-
for (const entry of permissionAudit.slice(0, 5)) {
|
|
431
|
-
const outcome = entry.approved === undefined ? 'pending' : entry.approved ? 'approved' : 'denied';
|
|
432
|
-
const outcomeColor = entry.approved === undefined ? C.warn : entry.approved ? C.good : C.bad;
|
|
433
|
-
lines.push(buildPanelLine(width, [
|
|
434
|
-
[' ', C.label],
|
|
435
|
-
[entry.tool, C.value],
|
|
436
|
-
[' ', C.label],
|
|
437
|
-
[entry.riskLevel.toUpperCase(), outcomeColor],
|
|
438
|
-
[' ', C.label],
|
|
439
|
-
[outcome, outcomeColor],
|
|
440
|
-
]));
|
|
441
|
-
lines.push(buildPanelLine(width, [
|
|
442
|
-
[' ', C.label],
|
|
443
|
-
[truncateDisplay(entry.summary, Math.max(0, width - 6)), C.dim],
|
|
444
|
-
]));
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (lintFindings.length > 0) {
|
|
449
|
-
lines.push(buildPanelLine(width, [[' Policy Lint', C.label]]));
|
|
450
|
-
for (const finding of lintFindings.slice(0, 5)) {
|
|
451
|
-
const color = finding.severity === 'error' ? C.bad : finding.severity === 'warn' ? C.warn : C.info;
|
|
452
|
-
lines.push(buildPanelLine(width, [
|
|
453
|
-
[' ', C.label],
|
|
454
|
-
[finding.severity.toUpperCase(), color],
|
|
455
|
-
[' ', C.label],
|
|
456
|
-
[truncateDisplay(finding.message, Math.max(0, width - 14)), color],
|
|
457
|
-
]));
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (preflightReview) {
|
|
462
|
-
lines.push(buildPanelLine(width, [[' Preflight Review', C.label]]));
|
|
463
|
-
const statusColor =
|
|
464
|
-
preflightReview.status === 'pass'
|
|
465
|
-
? C.good
|
|
466
|
-
: preflightReview.status === 'warn'
|
|
467
|
-
? C.warn
|
|
468
|
-
: C.bad;
|
|
469
|
-
lines.push(buildPanelLine(width, [
|
|
470
|
-
[' Status: ', C.label],
|
|
471
|
-
[preflightReview.status.toUpperCase(), statusColor],
|
|
472
|
-
[' Issues: ', C.label],
|
|
473
|
-
[String(preflightReview.issueCount), C.value],
|
|
474
|
-
[' Generated: ', C.label],
|
|
475
|
-
[fmtTime(preflightReview.generatedAt), C.dim],
|
|
476
|
-
]));
|
|
477
|
-
lines.push(buildPanelLine(width, [
|
|
478
|
-
[' ', C.label],
|
|
479
|
-
[truncateDisplay(preflightReview.summary, Math.max(0, width - 2)), C.dim],
|
|
480
|
-
]));
|
|
481
|
-
for (const issue of preflightReview.issues.slice(0, 4)) {
|
|
482
|
-
const issueColor = issue.severity === 'error' ? C.bad : issue.severity === 'warn' ? C.warn : C.info;
|
|
483
|
-
lines.push(buildPanelLine(width, [
|
|
484
|
-
[' ', C.label],
|
|
485
|
-
[issue.severity.toUpperCase(), issueColor],
|
|
486
|
-
[' ', C.label],
|
|
487
|
-
[truncateDisplay(issue.message, Math.max(0, width - 14)), issueColor],
|
|
488
|
-
]));
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
if (simulationSummary) {
|
|
493
|
-
lines.push(buildPanelLine(width, [[' Simulation Samples', C.label]]));
|
|
494
|
-
lines.push(buildPanelLine(width, [
|
|
495
|
-
[' Mode: ', C.label],
|
|
496
|
-
[simulationSummary.mode, C.info],
|
|
497
|
-
[' Diverged: ', C.label],
|
|
498
|
-
[`${simulationSummary.divergentScenarios}/${simulationSummary.totalScenarios}`, simulationSummary.divergentScenarios > 0 ? C.warn : C.good],
|
|
499
|
-
[' Allowed(actual/sim): ', C.label],
|
|
500
|
-
[`${simulationSummary.allowedByActual}/${simulationSummary.allowedBySimulated}`, C.value],
|
|
501
|
-
]));
|
|
502
|
-
for (const result of simulationSummary.results.slice(0, 4)) {
|
|
503
|
-
const color = result.diverged ? C.warn : (result.authoritativeDecision.allowed ? C.good : C.bad);
|
|
504
|
-
lines.push(buildPanelLine(width, [
|
|
505
|
-
[' ', C.label],
|
|
506
|
-
[truncateDisplay(result.scenario.label, Math.max(0, width - 40)), C.value],
|
|
507
|
-
[' ', C.label],
|
|
508
|
-
[(result.authoritativeDecision.allowed ? 'allow' : 'deny').toUpperCase(), color],
|
|
509
|
-
[' ', C.label],
|
|
510
|
-
[result.diverged ? (result.divergenceType ?? 'diverged') : 'aligned', color],
|
|
511
|
-
]));
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
return lines;
|
|
516
|
-
}
|
|
517
|
-
}
|