@pellux/goodvibes-tui 1.1.0 → 1.7.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 +20 -11
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +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/conversation-rendering.ts +19 -0
- 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/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +36 -0
- package/src/input/command-registry.ts +38 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- 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 +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- 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 +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- 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-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +15 -7
- 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 +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-feed-routes.ts +61 -23
- package/src/input/handler-feed.ts +47 -23
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +6 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +24 -27
- 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 +9 -43
- 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-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -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 +151 -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 +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -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 +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +13 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- 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/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/turn-injection.ts +114 -0
- package/src/runtime/bootstrap-command-context.ts +21 -1
- package/src/runtime/bootstrap-command-parts.ts +34 -7
- package/src/runtime/bootstrap-core.ts +14 -4
- package/src/runtime/bootstrap-shell.ts +29 -16
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- 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,252 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
3
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
4
|
-
import {
|
|
5
|
-
buildBodyText,
|
|
6
|
-
buildDetailBlock,
|
|
7
|
-
buildGuidanceLine,
|
|
8
|
-
buildKeyValueLine,
|
|
9
|
-
buildKeyboardHints,
|
|
10
|
-
buildPanelLine,
|
|
11
|
-
buildStatusBadge,
|
|
12
|
-
DEFAULT_PANEL_PALETTE,
|
|
13
|
-
type StatusBadgeKind,
|
|
14
|
-
} from './polish.ts';
|
|
15
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
16
|
-
import type { PolicyRuntimeState, PermissionAuditEntry } from '@/runtime/index.ts';
|
|
17
|
-
import { buildPermissionRuleSuggestions } from '@/runtime/index.ts';
|
|
18
|
-
|
|
19
|
-
// Splits a computed '/command arg1 arg2' string into the { name, args } shape
|
|
20
|
-
// ctx.executeCommand expects — same leading-slash-stripping approach used by
|
|
21
|
-
// remote-panel.ts / session-browser-panel.ts for their own dispatched commands.
|
|
22
|
-
function parseCommand(command: string): { name: string; args: string[] } | null {
|
|
23
|
-
const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
|
|
24
|
-
const [name, ...args] = parts;
|
|
25
|
-
return name ? { name, args } : null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Base chrome only — title band comes straight from DEFAULT_PANEL_PALETTE
|
|
29
|
-
// (WO-002).
|
|
30
|
-
const C = DEFAULT_PANEL_PALETTE;
|
|
31
|
-
|
|
32
|
-
// Reference catalog of approval lanes and where each one is reviewed. Used to
|
|
33
|
-
// resolve the next-step command for a live request and as a fallback reference
|
|
34
|
-
// when no live requests are present.
|
|
35
|
-
const LANE_REVIEW: Record<string, { command: string; why: string }> = {
|
|
36
|
-
shell: { command: '/security', why: 'side effects, destructive ops, secret exposure, escalation' },
|
|
37
|
-
file: { command: '/approval review file', why: 'config mutation, notebook edits, secret-bearing paths' },
|
|
38
|
-
network: { command: '/approval review network', why: 'external hosts, fetch scope, egress policy' },
|
|
39
|
-
delegate: { command: '/approval review delegate', why: 'recursive agents, spawn ceilings, write-set inheritance' },
|
|
40
|
-
mcp: { command: '/mcp trust', why: 'trust escalation, host scope, path scope, coherence mismatch' },
|
|
41
|
-
remote: { command: '/remote', why: 'runner trust, remote write scope, artifact requirements' },
|
|
42
|
-
hook: { command: '/hooks', why: 'deny/mutate authority, blocking behavior, runner provenance' },
|
|
43
|
-
plugin: { command: '/marketplace', why: 'install/update lifecycle, provenance, capability grants' },
|
|
44
|
-
sandbox: { command: '/sandbox review', why: 'WSL/VM isolation changes alter host risk posture' },
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
function laneOf(entry: PermissionAuditEntry): string {
|
|
48
|
-
const key = (entry.category || entry.tool || '').toLowerCase();
|
|
49
|
-
for (const lane of Object.keys(LANE_REVIEW)) {
|
|
50
|
-
if (key.includes(lane)) return lane;
|
|
51
|
-
}
|
|
52
|
-
return key || 'shell';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function reviewFor(entry: PermissionAuditEntry): { command: string; why: string } {
|
|
56
|
-
return LANE_REVIEW[laneOf(entry)] ?? { command: '/security', why: 'review approval posture' };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function decisionOf(entry: PermissionAuditEntry): 'pending' | 'approved' | 'denied' {
|
|
60
|
-
return entry.approved === undefined ? 'pending' : entry.approved ? 'approved' : 'denied';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function badgeKind(decision: 'pending' | 'approved' | 'denied'): StatusBadgeKind {
|
|
64
|
-
return decision === 'pending' ? 'pending' : decision === 'approved' ? 'completed' : 'failed';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function riskColor(risk: string): string {
|
|
68
|
-
const r = risk.toLowerCase();
|
|
69
|
-
if (r.includes('critical') || r.includes('high')) return C.bad;
|
|
70
|
-
if (r.includes('medium') || r.includes('moderate')) return C.warn;
|
|
71
|
-
return C.good;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function fmtAgo(ts: number): string {
|
|
75
|
-
const sec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
|
76
|
-
if (sec < 60) return `${sec}s ago`;
|
|
77
|
-
if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
|
|
78
|
-
return `${Math.floor(sec / 3600)}h ago`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export class ApprovalPanel extends ScrollableListPanel<PermissionAuditEntry> {
|
|
82
|
-
private readonly policyRuntimeState: Pick<PolicyRuntimeState, 'getSnapshot'>;
|
|
83
|
-
// p / 1-3 = dispatch '/policy simulate' or a rule-suggestion command via the
|
|
84
|
-
// handleInput -> handlePanelIntegrationAction bridge (handleInput has no
|
|
85
|
-
// ctx.executeCommand — same pattern as remote-panel.ts / session-browser-panel.ts).
|
|
86
|
-
private pendingCommand: { name: string; args: string[] } | null = null;
|
|
87
|
-
|
|
88
|
-
public constructor(policyRuntimeState: Pick<PolicyRuntimeState, 'getSnapshot'>) {
|
|
89
|
-
super('approval', 'Approval', 'A', 'security-policy');
|
|
90
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
91
|
-
this.policyRuntimeState = policyRuntimeState;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private getRuleSuggestions() {
|
|
95
|
-
const audit = this.policyRuntimeState.getSnapshot().recentPermissionAudit;
|
|
96
|
-
return buildPermissionRuleSuggestions(audit).slice(0, 3);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
override handleInput(key: string): boolean {
|
|
100
|
-
if (key === 'p') {
|
|
101
|
-
this.pendingCommand = parseCommand('/policy simulate');
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
if (key === '1' || key === '2' || key === '3') {
|
|
105
|
-
const suggestion = this.getRuleSuggestions()[Number(key) - 1];
|
|
106
|
-
if (!suggestion) return false;
|
|
107
|
-
this.pendingCommand = parseCommand(suggestion.command);
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
return super.handleInput(key);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Drains `pendingCommand` (set by 'p' or '1'/'2'/'3') via the ctx.executeCommand bridge. */
|
|
114
|
-
handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
115
|
-
if (!this.pendingCommand) return false;
|
|
116
|
-
const { name, args } = this.pendingCommand;
|
|
117
|
-
this.pendingCommand = null;
|
|
118
|
-
void ctx.executeCommand?.(name, args);
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
protected override getPalette() { return C; }
|
|
123
|
-
protected override getEmptyStateMessage() { return ' No approval pressure right now.'; }
|
|
124
|
-
protected override getEmptyStateActions() {
|
|
125
|
-
return [
|
|
126
|
-
{ command: '/security', summary: 'inspect trust, tokens, quarantines, and incident pressure' },
|
|
127
|
-
{ command: '/policy simulate', summary: 'preview which requests a rule change would auto-approve' },
|
|
128
|
-
];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Pending requests (those still needing a decision) are surfaced first so the
|
|
132
|
-
// operator sees actionable items at the top, then most-recent decisions.
|
|
133
|
-
protected getItems(): readonly PermissionAuditEntry[] {
|
|
134
|
-
const audit = this.policyRuntimeState.getSnapshot().recentPermissionAudit;
|
|
135
|
-
const pending = audit.filter((e) => e.approved === undefined);
|
|
136
|
-
const decided = audit.filter((e) => e.approved !== undefined);
|
|
137
|
-
return [...pending, ...decided];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
protected renderItem(entry: PermissionAuditEntry, _index: number, selected: boolean, width: number): Line {
|
|
141
|
-
const bg = selected ? C.selectBg : undefined;
|
|
142
|
-
const decision = decisionOf(entry);
|
|
143
|
-
const badge = buildStatusBadge(badgeKind(decision), decision)[0]!;
|
|
144
|
-
const detailWidth = Math.max(0, width - 40);
|
|
145
|
-
return buildPanelLine(width, [
|
|
146
|
-
[' ', C.label, bg],
|
|
147
|
-
[`${badge.text} `.padEnd(12), badge.fg, bg],
|
|
148
|
-
[`${truncateDisplay(entry.tool, 12)} `.padEnd(13), C.value, bg],
|
|
149
|
-
[`${truncateDisplay(entry.riskLevel, 6)} `.padEnd(7), riskColor(entry.riskLevel), bg],
|
|
150
|
-
[truncateDisplay(entry.summary, detailWidth), C.dim, bg],
|
|
151
|
-
]);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Next-step review command for the currently selected request (if any). */
|
|
155
|
-
public getSelectedCommand(): string | null {
|
|
156
|
-
const selected = this.getSelectedItem();
|
|
157
|
-
return selected ? reviewFor(selected).command : null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public render(width: number, height: number): Line[] {
|
|
161
|
-
this.clampSelection();
|
|
162
|
-
const audit = this.policyRuntimeState.getSnapshot().recentPermissionAudit;
|
|
163
|
-
const approvalCount = audit.filter((e) => e.approved === true).length;
|
|
164
|
-
const denialCount = audit.filter((e) => e.approved === false).length;
|
|
165
|
-
const pendingCount = audit.filter((e) => e.approved === undefined).length;
|
|
166
|
-
|
|
167
|
-
const selected = this.getSelectedItem() ?? null;
|
|
168
|
-
|
|
169
|
-
// ---- Posture summary (severity + counts first) ----
|
|
170
|
-
const headerLines: Line[] = [
|
|
171
|
-
buildPanelLine(width, [
|
|
172
|
-
[' ', C.label],
|
|
173
|
-
...buildStatusBadge('pending', 'pending', { count: pendingCount }),
|
|
174
|
-
[' ', C.dim],
|
|
175
|
-
...buildStatusBadge('completed', 'approved', { count: approvalCount }),
|
|
176
|
-
[' ', C.dim],
|
|
177
|
-
...buildStatusBadge('failed', 'denied', { count: denialCount }),
|
|
178
|
-
]),
|
|
179
|
-
pendingCount > 0
|
|
180
|
-
? buildPanelLine(width, [[` ${pendingCount} request${pendingCount !== 1 ? 's' : ''} awaiting a decision — select one to see its review path.`, C.warn]])
|
|
181
|
-
: buildPanelLine(width, [
|
|
182
|
-
[' p ', C.info],
|
|
183
|
-
['/policy simulate — preview which requests a scoped rule change would auto-approve', C.dim],
|
|
184
|
-
]),
|
|
185
|
-
];
|
|
186
|
-
|
|
187
|
-
// ---- Detail block for the selected request ----
|
|
188
|
-
const detailLines: Line[] = [];
|
|
189
|
-
if (selected) {
|
|
190
|
-
const review = reviewFor(selected);
|
|
191
|
-
const decision = decisionOf(selected);
|
|
192
|
-
detailLines.push(...buildDetailBlock(width, `Request · ${selected.tool}`, [
|
|
193
|
-
buildKeyValueLine(width, [
|
|
194
|
-
{ label: 'decision', value: decision, valueColor: decision === 'pending' ? C.info : decision === 'approved' ? C.good : C.bad },
|
|
195
|
-
{ label: 'risk', value: selected.riskLevel, valueColor: riskColor(selected.riskLevel) },
|
|
196
|
-
{ label: 'lane', value: laneOf(selected), valueColor: C.info },
|
|
197
|
-
], C),
|
|
198
|
-
buildKeyValueLine(width, [
|
|
199
|
-
{ label: 'requested', value: fmtAgo(selected.requestedAt), valueColor: C.dim },
|
|
200
|
-
...(selected.decidedAt ? [{ label: 'decided', value: fmtAgo(selected.decidedAt), valueColor: C.dim }] : []),
|
|
201
|
-
...(selected.target ? [{ label: 'target', value: truncateDisplay(selected.target, 24), valueColor: C.value }] : []),
|
|
202
|
-
], C),
|
|
203
|
-
...buildBodyText(width, selected.summary, C, C.value),
|
|
204
|
-
...(selected.reasons[0]
|
|
205
|
-
? buildBodyText(width, `why prompted: ${selected.reasons[0]}`, C, C.dim)
|
|
206
|
-
: buildBodyText(width, `why prompted: ${review.why}`, C, C.dim)),
|
|
207
|
-
// WO-160: dropped the printed `review.command` guidance line here —
|
|
208
|
-
// Enter is already wired by the shell to dispatch that exact command
|
|
209
|
-
// (see onSelect below and the "Enter: review (<command>)" footer
|
|
210
|
-
// hint), so restating it as a signpost was a pure action substitute.
|
|
211
|
-
], C));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ---- Durable-rule suggestions from repeated denials (dispatchable via 1/2/3) ----
|
|
215
|
-
const suggestions = this.getRuleSuggestions();
|
|
216
|
-
const ruleSuggestionLines: Line[] = [];
|
|
217
|
-
suggestions.forEach((suggestion, index) => {
|
|
218
|
-
ruleSuggestionLines.push(buildPanelLine(width, [
|
|
219
|
-
[` ${index + 1}) `, C.info],
|
|
220
|
-
[truncateDisplay(suggestion.summary, Math.max(0, width - 8)), C.info],
|
|
221
|
-
]));
|
|
222
|
-
ruleSuggestionLines.push(buildGuidanceLine(width, suggestion.command, suggestion.reason, C));
|
|
223
|
-
});
|
|
224
|
-
if (ruleSuggestionLines.length > 0) {
|
|
225
|
-
ruleSuggestionLines.unshift(buildPanelLine(width, [[' Suggested durable rules (press 1/2/3 to apply)', C.label]]));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ---- Context-aware footer: only show review key when a request is selected ----
|
|
229
|
-
const hints = selected
|
|
230
|
-
? [
|
|
231
|
-
{ keys: '↑/↓', label: 'select' },
|
|
232
|
-
{ keys: 'Enter', label: `review (${reviewFor(selected).command})` },
|
|
233
|
-
{ keys: 'g/G', label: 'top/bottom' },
|
|
234
|
-
]
|
|
235
|
-
: [
|
|
236
|
-
{ keys: '↑/↓', label: 'select' },
|
|
237
|
-
{ keys: 'g/G', label: 'top/bottom' },
|
|
238
|
-
];
|
|
239
|
-
if (suggestions.length > 0) hints.push({ keys: '1-3', label: 'apply rule suggestion' });
|
|
240
|
-
if (pendingCount === 0) hints.push({ keys: 'p', label: 'policy simulate' });
|
|
241
|
-
|
|
242
|
-
return this.renderList(width, height, {
|
|
243
|
-
title: 'Approval Control Room',
|
|
244
|
-
header: headerLines,
|
|
245
|
-
footer: [...detailLines, ...ruleSuggestionLines, buildKeyboardHints(width, hints, C)],
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
protected override onSelect(item: PermissionAuditEntry): void {
|
|
250
|
-
void item; // Enter is wired by the shell to open getSelectedCommand(); selection model lives here.
|
|
251
|
-
}
|
|
252
|
-
}
|