@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,447 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
|
-
import type { TokenAuditResult } from '@pellux/goodvibes-sdk/platform/security';
|
|
5
|
-
import type { UiReadModel, UiSecuritySnapshot } from '../runtime/ui-read-models.ts';
|
|
6
|
-
import {
|
|
7
|
-
buildAlignedRow,
|
|
8
|
-
buildEmptyState,
|
|
9
|
-
buildKeyboardHints,
|
|
10
|
-
buildPanelLine,
|
|
11
|
-
buildPanelWorkspace,
|
|
12
|
-
buildStatusPill,
|
|
13
|
-
DEFAULT_PANEL_PALETTE,
|
|
14
|
-
} from './polish.ts';
|
|
15
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
16
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
17
|
-
|
|
18
|
-
// Base chrome only — title band, state colors, and text tokens all come
|
|
19
|
-
// straight from DEFAULT_PANEL_PALETTE (WO-002).
|
|
20
|
-
const C = DEFAULT_PANEL_PALETTE;
|
|
21
|
-
|
|
22
|
-
function managedColor(managed: boolean): string {
|
|
23
|
-
return managed ? C.warn : C.info;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function resultColor(result: TokenAuditResult): string {
|
|
27
|
-
if (result.blocked) return C.bad;
|
|
28
|
-
if (result.scope.outcome === 'violation') return C.bad;
|
|
29
|
-
if (result.rotation.outcome === 'overdue') return C.bad;
|
|
30
|
-
if (result.rotation.outcome === 'warning') return C.warn;
|
|
31
|
-
return C.good;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function resultSummary(result: TokenAuditResult): string {
|
|
35
|
-
const parts: string[] = [];
|
|
36
|
-
if (result.scope.outcome === 'violation') parts.push(`scope:${result.scope.excessScopes.join(',')}`);
|
|
37
|
-
if (result.rotation.outcome === 'warning') parts.push('rotation warning');
|
|
38
|
-
if (result.rotation.outcome === 'overdue') parts.push('rotation overdue');
|
|
39
|
-
if (result.blocked) parts.push('blocked');
|
|
40
|
-
return parts.length > 0 ? parts.join(' | ') : 'in policy';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function severityColor(severity: 'low' | 'medium' | 'high' | 'critical'): string {
|
|
44
|
-
switch (severity) {
|
|
45
|
-
case 'critical':
|
|
46
|
-
case 'high':
|
|
47
|
-
return C.bad;
|
|
48
|
-
case 'medium':
|
|
49
|
-
return C.warn;
|
|
50
|
-
case 'low':
|
|
51
|
-
default:
|
|
52
|
-
return C.good;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Relative-time formatting for audit/rotation timestamps — humanized instead
|
|
57
|
-
// of raw ISO strings (WO-137). Mirrors the fmtAgo pattern already used by
|
|
58
|
-
// approval-panel.ts / debug-panel.ts / communication-panel.ts.
|
|
59
|
-
function fmtAgo(ts: number): string {
|
|
60
|
-
const sec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
|
61
|
-
if (sec < 5) return 'just now';
|
|
62
|
-
if (sec < 60) return `${sec}s ago`;
|
|
63
|
-
if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
|
|
64
|
-
if (sec < 86400) return `${Math.floor(sec / 3600)}h ago`;
|
|
65
|
-
return `${Math.floor(sec / 86400)}d ago`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function fmtDue(msUntilDue: number): string {
|
|
69
|
-
const overdue = msUntilDue < 0;
|
|
70
|
-
const sec = Math.floor(Math.abs(msUntilDue) / 1000);
|
|
71
|
-
const unit = sec < 3600
|
|
72
|
-
? `${Math.max(1, Math.floor(sec / 60))}m`
|
|
73
|
-
: sec < 86400
|
|
74
|
-
? `${Math.floor(sec / 3600)}h`
|
|
75
|
-
: `${Math.floor(sec / 86400)}d`;
|
|
76
|
-
return overdue ? `overdue by ${unit}` : `in ${unit}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Attack-path finding rows: 3 rendered lines each (severity/route, reason,
|
|
80
|
-
// evidence). Bounds the per-render window to the panel's actual height
|
|
81
|
-
// instead of a fixed "3 findings" cap, and pages through the rest via
|
|
82
|
-
// attackPathScroll ('[' / ']').
|
|
83
|
-
const ATTACK_PATH_FINDING_LINES = 3;
|
|
84
|
-
|
|
85
|
-
export class SecurityPanel extends ScrollableListPanel<TokenAuditResult> {
|
|
86
|
-
private readonly unsub: (() => void) | null;
|
|
87
|
-
/** Scroll offset into attackPathReview.findings (in finding units, not lines). */
|
|
88
|
-
private attackPathScroll = 0;
|
|
89
|
-
/** Set by 'f'; consumed by handlePanelIntegrationAction to dispatch /policy preflight. */
|
|
90
|
-
private pendingPreflight = false;
|
|
91
|
-
/** Set by 'i'; consumed by handlePanelIntegrationAction to jump to the incident panel. */
|
|
92
|
-
private pendingIncidentJump = false;
|
|
93
|
-
|
|
94
|
-
public constructor(private readonly readModel: UiReadModel<UiSecuritySnapshot>) {
|
|
95
|
-
super('security', 'Security', '▬', 'security-policy');
|
|
96
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
97
|
-
this.filterEnabled = true;
|
|
98
|
-
this.filterLabel = 'Filter tokens';
|
|
99
|
-
this.unsub = this.readModel.subscribe(() => this.markDirty());
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public override onDestroy(): void {
|
|
103
|
-
this.unsub?.();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
protected override getPalette() { return C; }
|
|
107
|
-
protected override getEmptyStateMessage() { return ' No API tokens are registered with the security auditor yet.'; }
|
|
108
|
-
protected override getEmptyStateActions() {
|
|
109
|
-
// WO-160: '/policy preflight' dropped — 'f' already dispatches it for
|
|
110
|
-
// real (see handleInput/handlePanelIntegrationAction) and is advertised
|
|
111
|
-
// as a live key hint in the footer even in this empty state, so listing
|
|
112
|
-
// it here too was a redundant action substitute. '/storage review' and
|
|
113
|
-
// '/mcp trust' stay: neither has an in-panel key equivalent.
|
|
114
|
-
return [
|
|
115
|
-
{ command: '/storage review', summary: 'inspect secure secret storage and environment overrides' },
|
|
116
|
-
{ command: '/mcp trust', summary: 'inspect active MCP trust and quarantine posture' },
|
|
117
|
-
];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
protected getItems(): readonly TokenAuditResult[] {
|
|
121
|
-
return this.readModel.getSnapshot().audit.results;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
protected renderItem(result: TokenAuditResult, index: number, selected: boolean, width: number): Line {
|
|
125
|
-
return buildAlignedRow(
|
|
126
|
-
width,
|
|
127
|
-
[
|
|
128
|
-
{ text: result.label, fg: C.value },
|
|
129
|
-
{ text: result.tokenId, fg: C.info },
|
|
130
|
-
{ text: result.scope.policyId, fg: C.label },
|
|
131
|
-
{ text: resultSummary(result), fg: resultColor(result) },
|
|
132
|
-
],
|
|
133
|
-
[
|
|
134
|
-
{ width: 22 },
|
|
135
|
-
{ width: 12 },
|
|
136
|
-
{ width: 10 },
|
|
137
|
-
{ width: Math.max(8, width - 50) },
|
|
138
|
-
],
|
|
139
|
-
{ selected, selectedBg: C.selectBg },
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
public handleInput(key: string): boolean {
|
|
144
|
-
if (!this.filterActive && key === 'r') {
|
|
145
|
-
// Real re-audit: force the read model to recompute (ApiTokenAuditor.auditAll
|
|
146
|
-
// under the hood) right now instead of waiting on the next incidental
|
|
147
|
-
// render, so lastAuditAt visibly advances the instant 'r' is pressed.
|
|
148
|
-
this.readModel.getSnapshot();
|
|
149
|
-
this.markDirty();
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
if (!this.filterActive && key === 'f') {
|
|
153
|
-
this.pendingPreflight = true;
|
|
154
|
-
this.markDirty();
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
if (!this.filterActive && key === 'i') {
|
|
158
|
-
if (!this.readModel.getSnapshot().latestIncident) return false;
|
|
159
|
-
this.pendingIncidentJump = true;
|
|
160
|
-
this.markDirty();
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
if (!this.filterActive && key === '[') {
|
|
164
|
-
this.attackPathScroll = Math.max(0, this.attackPathScroll - 1);
|
|
165
|
-
this.markDirty();
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
if (!this.filterActive && key === ']') {
|
|
169
|
-
this.attackPathScroll += 1;
|
|
170
|
-
this.markDirty();
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
return super.handleInput(key);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* f (preflight) and i (jump to incident) both require the integration
|
|
178
|
-
* context (executeCommand / panelManager), which is only available here —
|
|
179
|
-
* same staged-pending-action pattern as worktree-panel.ts and
|
|
180
|
-
* incident-review-panel.ts.
|
|
181
|
-
*/
|
|
182
|
-
public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
183
|
-
if (this.pendingPreflight) {
|
|
184
|
-
this.pendingPreflight = false;
|
|
185
|
-
if (!ctx.executeCommand) return false;
|
|
186
|
-
void ctx.executeCommand('policy', ['preflight']).catch(() => { /* surfaced via /policy output */ });
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
if (this.pendingIncidentJump) {
|
|
190
|
-
this.pendingIncidentJump = false;
|
|
191
|
-
ctx.panelManager.open('incident');
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
protected override filterMatches(result: TokenAuditResult, q: string): boolean {
|
|
198
|
-
return result.label.toLowerCase().includes(q)
|
|
199
|
-
|| result.scope.policyId.toLowerCase().includes(q)
|
|
200
|
-
|| result.tokenId.toLowerCase().includes(q);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
public render(width: number, height: number): Line[] {
|
|
204
|
-
this.clampSelection();
|
|
205
|
-
const snapshot = this.readModel.getSnapshot();
|
|
206
|
-
const view = snapshot.audit;
|
|
207
|
-
const preflightStatus = snapshot.policy.preflightStatus;
|
|
208
|
-
const preflightIssueCount = snapshot.policy.preflightIssueCount;
|
|
209
|
-
const lintFindingCount = snapshot.policy.lintFindingCount;
|
|
210
|
-
const quarantinedMcp = snapshot.mcpServers.filter((server) => server.schemaFreshness === 'quarantined');
|
|
211
|
-
const elevatedMcp = snapshot.mcpServers.filter((server) => server.trustMode === 'allow-all');
|
|
212
|
-
const incidents = snapshot.incidents;
|
|
213
|
-
const latestIncident = snapshot.latestIncident;
|
|
214
|
-
const quarantinedPlugins = snapshot.quarantinedPlugins;
|
|
215
|
-
const untrustedPlugins = snapshot.untrustedPlugins;
|
|
216
|
-
const attackPathReview = snapshot.attackPathReview;
|
|
217
|
-
const intro = 'Token audit, policy posture, MCP attack-path review, plugin trust, and incident pressure.';
|
|
218
|
-
// WO-160: dropped the printed '/policy preflight' guidance line — 'f' is
|
|
219
|
-
// already bound to dispatch it for real (see handleInput/
|
|
220
|
-
// handlePanelIntegrationAction below) and is advertised in the keyboard
|
|
221
|
-
// hints footer, so the printed command was a pure action substitute.
|
|
222
|
-
|
|
223
|
-
const governanceLines: Line[] = [
|
|
224
|
-
buildPanelLine(width, [
|
|
225
|
-
[' mode ', C.label],
|
|
226
|
-
[view.managed ? 'MANAGED' : 'ADVISORY', managedColor(view.managed)],
|
|
227
|
-
[' tokens ', C.label],
|
|
228
|
-
[String(view.totalTokens), C.value],
|
|
229
|
-
[' blocked ', C.label],
|
|
230
|
-
...buildStatusPill(view.blocked.length > 0 ? 'bad' : 'good', String(view.blocked.length)),
|
|
231
|
-
[' scope violations ', C.label],
|
|
232
|
-
...buildStatusPill(view.scopeViolations.length > 0 ? 'bad' : 'good', String(view.scopeViolations.length)),
|
|
233
|
-
[' overdue ', C.label],
|
|
234
|
-
...buildStatusPill(view.rotationOverdue.length > 0 ? 'bad' : 'good', String(view.rotationOverdue.length)),
|
|
235
|
-
[' warnings ', C.label],
|
|
236
|
-
...buildStatusPill(view.rotationWarnings.length > 0 ? 'warn' : 'good', String(view.rotationWarnings.length)),
|
|
237
|
-
]),
|
|
238
|
-
buildPanelLine(width, [
|
|
239
|
-
[' preflight ', C.label],
|
|
240
|
-
...buildStatusPill(preflightStatus === 'block' ? 'bad' : preflightStatus === 'warn' ? 'warn' : preflightStatus === 'pass' ? 'good' : 'info', preflightStatus.toUpperCase()),
|
|
241
|
-
[' issues ', C.label],
|
|
242
|
-
...buildStatusPill(preflightIssueCount > 0 ? 'warn' : 'good', String(preflightIssueCount)),
|
|
243
|
-
[' lint ', C.label],
|
|
244
|
-
...buildStatusPill(lintFindingCount > 0 ? 'warn' : 'good', String(lintFindingCount)),
|
|
245
|
-
[' denied permissions ', C.label],
|
|
246
|
-
...buildStatusPill(snapshot.deniedPermissions > 0 ? 'warn' : 'good', String(snapshot.deniedPermissions)),
|
|
247
|
-
]),
|
|
248
|
-
buildPanelLine(width, [
|
|
249
|
-
[' quarantined MCP ', C.label],
|
|
250
|
-
...buildStatusPill(quarantinedMcp.length > 0 ? 'bad' : 'good', String(quarantinedMcp.length)),
|
|
251
|
-
[' elevated MCP ', C.label],
|
|
252
|
-
...buildStatusPill(elevatedMcp.length > 0 ? 'warn' : 'good', String(elevatedMcp.length)),
|
|
253
|
-
[' quarantined plugins ', C.label],
|
|
254
|
-
...buildStatusPill(quarantinedPlugins.length > 0 ? 'bad' : 'good', String(quarantinedPlugins.length)),
|
|
255
|
-
[' untrusted plugins ', C.label],
|
|
256
|
-
...buildStatusPill(untrustedPlugins.length > 0 ? 'warn' : 'good', String(untrustedPlugins.length)),
|
|
257
|
-
]),
|
|
258
|
-
buildPanelLine(width, [
|
|
259
|
-
[' incidents ', C.label],
|
|
260
|
-
...buildStatusPill(incidents.length > 0 ? 'warn' : 'good', String(incidents.length)),
|
|
261
|
-
]),
|
|
262
|
-
];
|
|
263
|
-
|
|
264
|
-
const attackPathLines: Line[] = [
|
|
265
|
-
buildPanelLine(width, [
|
|
266
|
-
[' attack paths ', C.label],
|
|
267
|
-
...buildStatusPill(attackPathReview.criticalFindings > 0 ? 'bad' : 'good', String(attackPathReview.criticalFindings)),
|
|
268
|
-
[' critical ', C.label],
|
|
269
|
-
...buildStatusPill(attackPathReview.incoherentFindings > 0 ? 'warn' : 'good', String(attackPathReview.incoherentFindings)),
|
|
270
|
-
[' review ', C.label],
|
|
271
|
-
[truncateDisplay(attackPathReview.summary, Math.max(0, width - 36)), C.dim],
|
|
272
|
-
]),
|
|
273
|
-
];
|
|
274
|
-
|
|
275
|
-
// Empty state: no token results yet — show governance + threat posture before base empty state
|
|
276
|
-
if (view.results.length === 0) {
|
|
277
|
-
const emptyStateLines = [
|
|
278
|
-
...governanceLines,
|
|
279
|
-
...buildEmptyState(
|
|
280
|
-
width,
|
|
281
|
-
this.getEmptyStateMessage(),
|
|
282
|
-
'The security control room can already review policy, MCP, plugin, and incident posture, but token-specific scope and rotation audit data has not been registered.',
|
|
283
|
-
this.getEmptyStateActions(),
|
|
284
|
-
C,
|
|
285
|
-
),
|
|
286
|
-
];
|
|
287
|
-
if (quarantinedMcp.length > 0) {
|
|
288
|
-
emptyStateLines.push(buildPanelLine(width, [[' MCP quarantine still active despite no registered tokens.', C.warn]]));
|
|
289
|
-
}
|
|
290
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
291
|
-
title: 'Security Control Room',
|
|
292
|
-
intro,
|
|
293
|
-
sections: [
|
|
294
|
-
{ title: 'Governance', lines: emptyStateLines },
|
|
295
|
-
{ title: 'Attack Paths', lines: attackPathLines },
|
|
296
|
-
],
|
|
297
|
-
footerLines: [buildKeyboardHints(width, [{ keys: 'f', label: 'preflight' }], C)],
|
|
298
|
-
palette: C,
|
|
299
|
-
});
|
|
300
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
301
|
-
return workspace.slice(0, height);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (attackPathReview.findings.length > 0) {
|
|
305
|
-
attackPathLines.push(buildPanelLine(width, [[' MCP attack-path review', C.label]]));
|
|
306
|
-
// Fully scrollable — no fixed "3 findings" cap. The visible window
|
|
307
|
-
// scales with the panel's actual height; '[' / ']' page through the
|
|
308
|
-
// rest when there are more findings than currently fit.
|
|
309
|
-
const windowSize = Math.max(1, Math.floor((height - 16) / ATTACK_PATH_FINDING_LINES));
|
|
310
|
-
const maxScroll = Math.max(0, attackPathReview.findings.length - windowSize);
|
|
311
|
-
this.attackPathScroll = Math.min(this.attackPathScroll, maxScroll);
|
|
312
|
-
const start = this.attackPathScroll;
|
|
313
|
-
const end = Math.min(attackPathReview.findings.length, start + windowSize);
|
|
314
|
-
for (const finding of attackPathReview.findings.slice(start, end)) {
|
|
315
|
-
attackPathLines.push(buildPanelLine(width, [[
|
|
316
|
-
truncateDisplay(` ${finding.severity.toUpperCase()} ${finding.serverName}: ${finding.route}`, width),
|
|
317
|
-
severityColor(finding.severity),
|
|
318
|
-
]]));
|
|
319
|
-
attackPathLines.push(buildPanelLine(width, [[
|
|
320
|
-
truncateDisplay(` ${finding.reason}`, width),
|
|
321
|
-
C.dim,
|
|
322
|
-
]]));
|
|
323
|
-
attackPathLines.push(buildPanelLine(width, [[
|
|
324
|
-
truncateDisplay(` evidence: ${finding.evidence.join(' | ')}`, width),
|
|
325
|
-
C.dim,
|
|
326
|
-
]]));
|
|
327
|
-
}
|
|
328
|
-
if (attackPathReview.findings.length > windowSize) {
|
|
329
|
-
attackPathLines.push(buildPanelLine(width, [[
|
|
330
|
-
` showing ${start + 1}-${end} of ${attackPathReview.findings.length} findings ([ / ] to scroll)`,
|
|
331
|
-
C.dim,
|
|
332
|
-
]]));
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Column header for the token audit list so the aligned columns are legible.
|
|
337
|
-
const listHeader: Line[] = [
|
|
338
|
-
...governanceLines,
|
|
339
|
-
buildAlignedRow(
|
|
340
|
-
width,
|
|
341
|
-
[
|
|
342
|
-
{ text: 'TOKEN LABEL', fg: C.label, bold: true },
|
|
343
|
-
{ text: 'TOKEN ID', fg: C.label, bold: true },
|
|
344
|
-
{ text: 'POLICY', fg: C.label, bold: true },
|
|
345
|
-
{ text: `STATUS (${view.results.length} audited)`, fg: C.label, bold: true },
|
|
346
|
-
],
|
|
347
|
-
[
|
|
348
|
-
{ width: 22 },
|
|
349
|
-
{ width: 12 },
|
|
350
|
-
{ width: 10 },
|
|
351
|
-
{ width: Math.max(8, width - 50) },
|
|
352
|
-
],
|
|
353
|
-
{},
|
|
354
|
-
),
|
|
355
|
-
];
|
|
356
|
-
|
|
357
|
-
// Detail must track the filtered view the list highlights, not the raw
|
|
358
|
-
// audit results — under an applied token filter they diverge.
|
|
359
|
-
const selected = this.getSelectedItem();
|
|
360
|
-
const detailLines: Line[] = [];
|
|
361
|
-
if (selected) {
|
|
362
|
-
detailLines.push(buildPanelLine(width, [
|
|
363
|
-
[' Token: ', C.label],
|
|
364
|
-
[selected.label, C.value],
|
|
365
|
-
[' Policy: ', C.label],
|
|
366
|
-
[selected.scope.policyId, C.info],
|
|
367
|
-
[' Blocked: ', C.label],
|
|
368
|
-
[selected.blocked ? 'yes' : 'no', selected.blocked ? C.bad : C.good],
|
|
369
|
-
]));
|
|
370
|
-
detailLines.push(buildPanelLine(width, [
|
|
371
|
-
[' Scope: ', C.label],
|
|
372
|
-
[selected.scope.outcome, selected.scope.outcome === 'violation' ? C.bad : C.good],
|
|
373
|
-
[' Excess: ', C.label],
|
|
374
|
-
[truncateDisplay(selected.scope.excessScopes.length > 0 ? selected.scope.excessScopes.join(', ') : 'none', Math.max(0, width - 27)), selected.scope.excessScopes.length > 0 ? C.bad : C.dim],
|
|
375
|
-
]));
|
|
376
|
-
detailLines.push(buildPanelLine(width, [
|
|
377
|
-
[' Rotation: ', C.label],
|
|
378
|
-
[selected.rotation.outcome, selected.rotation.outcome === 'ok' ? C.good : selected.rotation.outcome === 'warning' ? C.warn : C.bad],
|
|
379
|
-
[' Due: ', C.label],
|
|
380
|
-
[fmtDue(selected.rotation.msUntilDue), selected.rotation.msUntilDue < 0 ? C.bad : C.value],
|
|
381
|
-
[' Age(d): ', C.label],
|
|
382
|
-
[String(Math.floor(selected.rotation.ageMs / (24 * 60 * 60 * 1000))), C.value],
|
|
383
|
-
]));
|
|
384
|
-
detailLines.push(buildPanelLine(width, [[
|
|
385
|
-
`Last audit: ${view.lastAuditAt ? fmtAgo(view.lastAuditAt) : 'never'} Press r to refresh.`,
|
|
386
|
-
C.dim,
|
|
387
|
-
]]));
|
|
388
|
-
if (preflightStatus !== 'n/a') {
|
|
389
|
-
detailLines.push(buildPanelLine(width, [[
|
|
390
|
-
truncateDisplay(`Policy preflight: ${preflightStatus} (${preflightIssueCount} issue${preflightIssueCount === 1 ? '' : 's'})`, width),
|
|
391
|
-
preflightStatus === 'block' ? C.bad : preflightStatus === 'warn' ? C.warn : C.dim,
|
|
392
|
-
]]));
|
|
393
|
-
}
|
|
394
|
-
if (quarantinedMcp.length > 0) {
|
|
395
|
-
const server = quarantinedMcp[0]!;
|
|
396
|
-
detailLines.push(buildPanelLine(width, [[
|
|
397
|
-
truncateDisplay(`MCP quarantine: ${server.name} ${server.quarantineReason ?? 'unknown'}${server.quarantineDetail ? ` - ${server.quarantineDetail}` : ''}`, width),
|
|
398
|
-
C.bad,
|
|
399
|
-
]]));
|
|
400
|
-
}
|
|
401
|
-
if (quarantinedPlugins.length > 0) {
|
|
402
|
-
const plugin = quarantinedPlugins[0]!;
|
|
403
|
-
detailLines.push(buildPanelLine(width, [[
|
|
404
|
-
truncateDisplay(`Plugin quarantine: ${plugin.name} (${plugin.trustTier})`, width),
|
|
405
|
-
C.bad,
|
|
406
|
-
]]));
|
|
407
|
-
} else if (untrustedPlugins.length > 0) {
|
|
408
|
-
const plugin = untrustedPlugins[0]!;
|
|
409
|
-
detailLines.push(buildPanelLine(width, [[
|
|
410
|
-
truncateDisplay(`Plugin trust warning: ${plugin.name} remains untrusted.`, width),
|
|
411
|
-
C.warn,
|
|
412
|
-
]]));
|
|
413
|
-
}
|
|
414
|
-
if (latestIncident) {
|
|
415
|
-
detailLines.push(buildPanelLine(width, [[
|
|
416
|
-
truncateDisplay(`Latest incident: ${latestIncident.classification} - ${latestIncident.summary}`, width),
|
|
417
|
-
C.warn,
|
|
418
|
-
]]));
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const hints = this.filterActive
|
|
423
|
-
? [
|
|
424
|
-
{ keys: 'type', label: 'filter tokens' },
|
|
425
|
-
{ keys: 'Enter', label: 'apply' },
|
|
426
|
-
{ keys: 'Esc', label: 'clear' },
|
|
427
|
-
]
|
|
428
|
-
: [
|
|
429
|
-
{ keys: '↑/↓', label: 'select token' },
|
|
430
|
-
{ keys: '/', label: 'filter' },
|
|
431
|
-
{ keys: 'r', label: 'refresh audit' },
|
|
432
|
-
{ keys: 'f', label: 'preflight' },
|
|
433
|
-
...(latestIncident ? [{ keys: 'i', label: 'jump to incident' }] : []),
|
|
434
|
-
...(attackPathReview.findings.length > 0 ? [{ keys: '[ / ]', label: 'scroll attack paths' }] : []),
|
|
435
|
-
];
|
|
436
|
-
|
|
437
|
-
return this.renderList(width, height, {
|
|
438
|
-
title: 'Security Control Room',
|
|
439
|
-
header: listHeader,
|
|
440
|
-
footer: [
|
|
441
|
-
...detailLines,
|
|
442
|
-
...attackPathLines,
|
|
443
|
-
],
|
|
444
|
-
hints,
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
}
|