@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,406 +0,0 @@
|
|
|
1
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
2
|
-
import type { Line } from '../types/grid.ts';
|
|
3
|
-
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
4
|
-
import { formatShortDuration } from '../utils/format-duration.ts';
|
|
5
|
-
import type {
|
|
6
|
-
ForensicsRegistry,
|
|
7
|
-
FailureReport,
|
|
8
|
-
ForensicsBundle,
|
|
9
|
-
CausalChainEntry,
|
|
10
|
-
PhaseTimingEntry,
|
|
11
|
-
ForensicsJumpLink,
|
|
12
|
-
} from '@/runtime/index.ts';
|
|
13
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
14
|
-
import {
|
|
15
|
-
buildBodyText,
|
|
16
|
-
buildEmptyState,
|
|
17
|
-
buildGuidanceLine,
|
|
18
|
-
buildKeyValueLine,
|
|
19
|
-
buildPanelLine,
|
|
20
|
-
buildPanelWorkspace,
|
|
21
|
-
buildStatusPill,
|
|
22
|
-
DEFAULT_PANEL_PALETTE,
|
|
23
|
-
type PanelPalette,
|
|
24
|
-
} from './polish.ts';
|
|
25
|
-
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
26
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
27
|
-
|
|
28
|
-
// Base chrome only — title band, state colors, and text tokens all come
|
|
29
|
-
// straight from DEFAULT_PANEL_PALETTE (WO-002).
|
|
30
|
-
const C = DEFAULT_PANEL_PALETTE;
|
|
31
|
-
|
|
32
|
-
function classificationColor(value: string): string {
|
|
33
|
-
switch (value) {
|
|
34
|
-
case 'cancelled':
|
|
35
|
-
return C.dim;
|
|
36
|
-
case 'max_tokens':
|
|
37
|
-
case 'unknown':
|
|
38
|
-
return C.warn;
|
|
39
|
-
default:
|
|
40
|
-
return C.bad;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function fmtTime(ts: number): string {
|
|
45
|
-
const d = new Date(ts);
|
|
46
|
-
const hh = String(d.getHours()).padStart(2, '0');
|
|
47
|
-
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
48
|
-
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
49
|
-
return `${hh}:${mm}:${ss}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Default workspace-relative destination for a panel-triggered `x` export.
|
|
54
|
-
* Left relative (not resolved against home/cwd here) so the `/incident
|
|
55
|
-
* export` command's own `shellPaths.resolveWorkspacePath` — the composition
|
|
56
|
-
* root's owned path resolver — decides the final absolute location.
|
|
57
|
-
*/
|
|
58
|
-
function buildIncidentExportPath(id: string): string {
|
|
59
|
-
return `goodvibes-exports/incident-${id}-${Date.now()}.json`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export class IncidentReviewPanel extends ScrollableListPanel<FailureReport> {
|
|
63
|
-
private readonly registry?: ForensicsRegistry;
|
|
64
|
-
private readonly unsub: (() => void) | null;
|
|
65
|
-
|
|
66
|
-
// Inline confirm for the (non-destructive but deliberate) capture action.
|
|
67
|
-
private _confirm: ConfirmState<string> | null = null;
|
|
68
|
-
|
|
69
|
-
// Pending cross-panel actions resolved via handlePanelIntegrationAction,
|
|
70
|
-
// which is the only place `executeCommand`/`panelManager` are available.
|
|
71
|
-
private _pendingExport: string | null = null;
|
|
72
|
-
private _pendingCapture: string | null = null;
|
|
73
|
-
private _pendingJump: ForensicsJumpLink | null = null;
|
|
74
|
-
|
|
75
|
-
// Cache buildBundle(id) per selected report id instead of recomputing it
|
|
76
|
-
// on every render call; invalidated whenever the registry changes.
|
|
77
|
-
private _bundleCache: { readonly id: string; readonly bundle: ForensicsBundle | undefined } | null = null;
|
|
78
|
-
|
|
79
|
-
public constructor(registry?: ForensicsRegistry) {
|
|
80
|
-
super('incident', 'Incident Review', '◪', 'incidents-diagnostics');
|
|
81
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
82
|
-
this.filterEnabled = true;
|
|
83
|
-
this.filterLabel = 'Filter incidents';
|
|
84
|
-
this.registry = registry;
|
|
85
|
-
this.unsub = registry
|
|
86
|
-
? registry.subscribe(() => {
|
|
87
|
-
this._bundleCache = null;
|
|
88
|
-
this.markDirty();
|
|
89
|
-
})
|
|
90
|
-
: null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public override onDestroy(): void {
|
|
94
|
-
this.unsub?.();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
protected override getPalette(): PanelPalette {
|
|
98
|
-
return C;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
protected getItems(): readonly FailureReport[] {
|
|
102
|
-
return this.registry?.getAll() ?? [];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
protected override filterMatches(report: FailureReport, q: string): boolean {
|
|
106
|
-
return report.classification.toLowerCase().includes(q)
|
|
107
|
-
|| report.id.toLowerCase().includes(q)
|
|
108
|
-
|| (report.summary ?? '').toLowerCase().includes(q);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
protected renderItem(report: FailureReport, index: number, selected: boolean, width: number): Line {
|
|
112
|
-
const bg = selected ? C.selectBg : undefined;
|
|
113
|
-
return buildPanelLine(width, [
|
|
114
|
-
[' ', C.label, bg],
|
|
115
|
-
[fitDisplay(report.id, 9), C.dim, bg],
|
|
116
|
-
[fitDisplay(report.classification, 20), classificationColor(report.classification), bg],
|
|
117
|
-
[truncateDisplay(report.summary, Math.max(0, width - 31)), C.value, bg],
|
|
118
|
-
]);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
protected override getEmptyStateMessage(): string {
|
|
122
|
-
return ' No incidents recorded yet.';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
126
|
-
return [
|
|
127
|
-
{ command: '/incident latest', summary: 'inspect the latest report once one exists' },
|
|
128
|
-
{ command: '/recall capture incident latest', summary: 'promote incident evidence into project knowledge' },
|
|
129
|
-
];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private getBundle(id: string): ForensicsBundle | undefined {
|
|
133
|
-
if (!this.registry) return undefined;
|
|
134
|
-
if (!this._bundleCache || this._bundleCache.id !== id) {
|
|
135
|
-
this._bundleCache = { id, bundle: this.registry.buildBundle(id) };
|
|
136
|
-
}
|
|
137
|
-
return this._bundleCache.bundle;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private _pushPhaseTimingLine(lines: Line[], pt: PhaseTimingEntry, width: number): void {
|
|
141
|
-
const statusChar = pt.success ? '✓' : '✕';
|
|
142
|
-
const statusColor = pt.success ? C.good : C.bad;
|
|
143
|
-
const dur = formatShortDuration(pt.durationMs);
|
|
144
|
-
const phaseLabel = fitDisplay(pt.phase, 14);
|
|
145
|
-
const errPart = pt.error ? ` ${truncateDisplay(pt.error, Math.max(0, width - 34))}` : '';
|
|
146
|
-
lines.push(buildPanelLine(width, [
|
|
147
|
-
[' ', C.dim],
|
|
148
|
-
[`${statusChar} `, statusColor],
|
|
149
|
-
[phaseLabel, C.value],
|
|
150
|
-
[dur.padStart(8, ' '), C.label],
|
|
151
|
-
[errPart, C.bad],
|
|
152
|
-
]));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
private _pushCausalLine(lines: Line[], entry: CausalChainEntry, width: number): void {
|
|
156
|
-
const prefix = entry.isRootCause ? ' * ' : ' - ';
|
|
157
|
-
const color = entry.isRootCause ? C.bad : C.dim;
|
|
158
|
-
const timeStr = fmtTime(entry.ts);
|
|
159
|
-
const descMax = Math.max(0, width - prefix.length - 9);
|
|
160
|
-
lines.push(buildPanelLine(width, [
|
|
161
|
-
[prefix, color],
|
|
162
|
-
[`${timeStr} `, C.label],
|
|
163
|
-
[truncateDisplay(entry.description, descMax), color],
|
|
164
|
-
]));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private _pushJumpLinkLine(lines: Line[], link: ForensicsJumpLink, width: number): void {
|
|
168
|
-
const kindTag = link.kind === 'panel' ? '[panel]' : '[cmd] ';
|
|
169
|
-
lines.push(buildPanelLine(width, [
|
|
170
|
-
[' ', C.dim],
|
|
171
|
-
[kindTag, C.label],
|
|
172
|
-
[` ${link.label}`, C.info],
|
|
173
|
-
[link.args ? ` (${link.args})` : '', C.dim],
|
|
174
|
-
]));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Decision-making for every key: navigation is delegated to
|
|
179
|
-
* ScrollableListPanel, while `x`/`c`/`j` and any in-progress confirm are
|
|
180
|
-
* resolved here and staged as a pending action. The actual cross-panel
|
|
181
|
-
* side effect (executeCommand / panelManager.open) requires the
|
|
182
|
-
* PanelIntegrationContext, which is only available in
|
|
183
|
-
* `handlePanelIntegrationAction` — invoked immediately after this method
|
|
184
|
-
* returns `true` for the same key.
|
|
185
|
-
*/
|
|
186
|
-
public override handleInput(key: string): boolean {
|
|
187
|
-
if (this.lastError !== null) this.clearError();
|
|
188
|
-
|
|
189
|
-
if (this._confirm) {
|
|
190
|
-
const outcome = handleConfirmInput(this._confirm, key);
|
|
191
|
-
if (outcome === 'confirmed') {
|
|
192
|
-
this._pendingCapture = this._confirm.subject;
|
|
193
|
-
this._confirm = null;
|
|
194
|
-
this.markDirty();
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
if (outcome === 'cancelled') {
|
|
198
|
-
this._confirm = null;
|
|
199
|
-
this.markDirty();
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
|
-
return true; // absorbed — keep the confirm dialog pending
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Actions must target the filtered view the list highlights — raw
|
|
206
|
-
// getItems() desyncs selectedIndex under an applied '/' filter.
|
|
207
|
-
const reports = this.getVisibleItems();
|
|
208
|
-
if (reports.length > 0 && (key === 'x' || key === 'c' || key === 'j')) {
|
|
209
|
-
this.clampSelection();
|
|
210
|
-
const selected = this.getSelectedItem();
|
|
211
|
-
if (selected) {
|
|
212
|
-
if (key === 'x') {
|
|
213
|
-
this._pendingExport = selected.id;
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
if (key === 'c') {
|
|
217
|
-
this._confirm = { subject: selected.id, label: `incident ${selected.id}`, verb: 'Capture' };
|
|
218
|
-
this.markDirty();
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
if (key === 'j') {
|
|
222
|
-
const link = selected.jumpLinks[0];
|
|
223
|
-
if (link) {
|
|
224
|
-
this._pendingJump = link;
|
|
225
|
-
return true;
|
|
226
|
-
}
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return super.handleInput(key);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
236
|
-
if (this._pendingExport) {
|
|
237
|
-
const id = this._pendingExport;
|
|
238
|
-
this._pendingExport = null;
|
|
239
|
-
const path = buildIncidentExportPath(id);
|
|
240
|
-
void ctx.executeCommand?.('incident', ['export', id, path]).catch((err) => {
|
|
241
|
-
logger.debug('incident export dispatch failed', { err });
|
|
242
|
-
});
|
|
243
|
-
return true;
|
|
244
|
-
}
|
|
245
|
-
if (this._pendingCapture) {
|
|
246
|
-
const id = this._pendingCapture;
|
|
247
|
-
this._pendingCapture = null;
|
|
248
|
-
void ctx.executeCommand?.('incident', ['capture', id]).catch((err) => {
|
|
249
|
-
logger.debug('incident capture dispatch failed', { err });
|
|
250
|
-
});
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
if (this._pendingJump) {
|
|
254
|
-
const link = this._pendingJump;
|
|
255
|
-
this._pendingJump = null;
|
|
256
|
-
if (link.kind === 'panel') {
|
|
257
|
-
ctx.panelManager.open(link.target);
|
|
258
|
-
} else {
|
|
259
|
-
void ctx.executeCommand?.(link.target, link.args ? link.args.split(/\s+/).filter(Boolean) : []).catch((err) => {
|
|
260
|
-
logger.debug('jump link dispatch failed', { err });
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
public render(width: number, height: number): Line[] {
|
|
269
|
-
if (this._confirm) {
|
|
270
|
-
return buildPanelWorkspace(width, height, {
|
|
271
|
-
title: 'Incident Review Workspace',
|
|
272
|
-
sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this._confirm) }],
|
|
273
|
-
palette: C,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const intro = 'Failure bundles with causal chains, phase timings, jump links, and exportable review evidence.';
|
|
278
|
-
|
|
279
|
-
if (!this.registry) {
|
|
280
|
-
return buildPanelWorkspace(width, height, {
|
|
281
|
-
title: 'Incident Review Workspace',
|
|
282
|
-
intro,
|
|
283
|
-
sections: [{
|
|
284
|
-
lines: buildEmptyState(
|
|
285
|
-
width,
|
|
286
|
-
' Incident registry not configured for this session.',
|
|
287
|
-
'This runtime was not wired with a forensics registry at bootstrap, so no incident data is available.',
|
|
288
|
-
[],
|
|
289
|
-
C,
|
|
290
|
-
),
|
|
291
|
-
}],
|
|
292
|
-
palette: C,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const reports = this.getItems();
|
|
297
|
-
if (reports.length === 0) {
|
|
298
|
-
return this.renderList(width, height, { title: 'Incident Review Workspace' });
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
this.clampSelection();
|
|
302
|
-
// Header/detail must describe the row the (possibly filtered) list
|
|
303
|
-
// highlights; a filter matching nothing leaves no selection at all.
|
|
304
|
-
const visible = this.getVisibleItems();
|
|
305
|
-
const selected = this.getSelectedItem();
|
|
306
|
-
if (!selected) {
|
|
307
|
-
return this.renderList(width, height, { title: 'Incident Review Workspace' });
|
|
308
|
-
}
|
|
309
|
-
const bundle = this.getBundle(selected.id);
|
|
310
|
-
|
|
311
|
-
const headerLines: Line[] = [
|
|
312
|
-
buildKeyValueLine(width, [
|
|
313
|
-
{ label: 'incidents', value: String(reports.length), valueColor: C.value },
|
|
314
|
-
{ label: 'selected', value: `${this.selectedIndex + 1}/${visible.length}`, valueColor: C.info },
|
|
315
|
-
{ label: 'classification', value: selected.classification, valueColor: classificationColor(selected.classification) },
|
|
316
|
-
], C),
|
|
317
|
-
];
|
|
318
|
-
|
|
319
|
-
const footerLines: Line[] = [];
|
|
320
|
-
if (bundle) {
|
|
321
|
-
footerLines.push(buildKeyValueLine(width, [
|
|
322
|
-
{ label: 'id', value: selected.id, valueColor: C.dim },
|
|
323
|
-
{ label: 'trace', value: selected.traceId, valueColor: C.dim },
|
|
324
|
-
], C));
|
|
325
|
-
footerLines.push(...buildBodyText(width, `Root cause: ${bundle.evidence.rootCause ?? 'n/a'}`, C, C.value));
|
|
326
|
-
footerLines.push(buildKeyValueLine(width, [
|
|
327
|
-
{ label: 'Permissions denied', value: String(bundle.evidence.deniedPermissionCount), valueColor: bundle.evidence.deniedPermissionCount > 0 ? C.warn : C.dim },
|
|
328
|
-
{ label: 'Budget breaches', value: String(bundle.evidence.budgetBreachCount), valueColor: bundle.evidence.budgetBreachCount > 0 ? C.warn : C.dim },
|
|
329
|
-
{ label: 'Replay mismatches', value: String(bundle.replay.mismatchCount), valueColor: bundle.replay.mismatchCount > 0 ? C.bad : C.dim },
|
|
330
|
-
], C));
|
|
331
|
-
footerLines.push(buildPanelLine(width, [
|
|
332
|
-
[' Related IDs: ', C.label],
|
|
333
|
-
[truncateDisplay(`turn=${bundle.evidence.relatedIds.turnId ?? 'n/a'} task=${bundle.evidence.relatedIds.taskId ?? 'n/a'} agent=${bundle.evidence.relatedIds.agentId ?? 'n/a'}`, Math.max(0, width - 14)), C.info],
|
|
334
|
-
]));
|
|
335
|
-
if (selected.phaseTimings.length > 0) {
|
|
336
|
-
footerLines.push(buildPanelLine(width, [[' Phase Timings:', C.label]]));
|
|
337
|
-
for (const pt of selected.phaseTimings) this._pushPhaseTimingLine(footerLines, pt, width);
|
|
338
|
-
}
|
|
339
|
-
if (selected.causalChain.length > 0) {
|
|
340
|
-
footerLines.push(buildPanelLine(width, [[' Causal Chain:', C.label]]));
|
|
341
|
-
for (const entry of selected.causalChain) this._pushCausalLine(footerLines, entry, width);
|
|
342
|
-
}
|
|
343
|
-
const denied = selected.permissionEvidence.find((entry) => entry.approved === false);
|
|
344
|
-
if (denied) {
|
|
345
|
-
footerLines.push(buildPanelLine(width, [
|
|
346
|
-
[' Permission: ', C.label],
|
|
347
|
-
[truncateDisplay(`${denied.tool} denied${denied.riskLevel ? ` (${denied.riskLevel})` : ''}${denied.summary ? ` - ${denied.summary}` : ''}`, Math.max(0, width - 14)), C.warn],
|
|
348
|
-
]));
|
|
349
|
-
}
|
|
350
|
-
if (bundle.replay.relatedMismatches.length > 0) {
|
|
351
|
-
const mismatch = bundle.replay.relatedMismatches[0]!;
|
|
352
|
-
const ownerBreakdown = Object.entries(bundle.replay.mismatchBreakdown.byOwnerDomain)
|
|
353
|
-
.filter(([, count]) => count > 0)
|
|
354
|
-
.slice(0, 3)
|
|
355
|
-
.map(([domain, count]) => `${domain}:${count}`)
|
|
356
|
-
.join(', ');
|
|
357
|
-
const replayDetail = ownerBreakdown.length > 0
|
|
358
|
-
? `Replay link: ${mismatch.kind}${mismatch.ownerDomain ? `/${mismatch.ownerDomain}` : ''} - ${mismatch.description} Replay owners: ${ownerBreakdown}`
|
|
359
|
-
: `Replay link: ${mismatch.kind}${mismatch.ownerDomain ? `/${mismatch.ownerDomain}` : ''} - ${mismatch.description}`;
|
|
360
|
-
footerLines.push(buildPanelLine(width, [
|
|
361
|
-
[' ', C.label],
|
|
362
|
-
...buildStatusPill('bad', truncateDisplay(replayDetail, Math.max(0, width - 2))),
|
|
363
|
-
]));
|
|
364
|
-
} else {
|
|
365
|
-
const ownerBreakdown = Object.entries(bundle.replay.mismatchBreakdown.byOwnerDomain)
|
|
366
|
-
.filter(([, count]) => count > 0)
|
|
367
|
-
.slice(0, 3)
|
|
368
|
-
.map(([domain, count]) => `${domain}:${count}`)
|
|
369
|
-
.join(', ');
|
|
370
|
-
if (ownerBreakdown.length > 0) {
|
|
371
|
-
footerLines.push(buildPanelLine(width, [
|
|
372
|
-
[' Replay owners: ', C.label],
|
|
373
|
-
[truncateDisplay(ownerBreakdown, Math.max(0, width - 17)), C.info],
|
|
374
|
-
]));
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (selected.jumpLinks.length > 0) {
|
|
378
|
-
footerLines.push(buildPanelLine(width, [[' Jump Links:', C.label]]));
|
|
379
|
-
for (const link of selected.jumpLinks) this._pushJumpLinkLine(footerLines, link, width);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
footerLines.push(buildGuidanceLine(width, '/security', 'open the broader trust and incident posture control room', C));
|
|
383
|
-
|
|
384
|
-
const hints = this.filterActive
|
|
385
|
-
? [
|
|
386
|
-
{ keys: 'type', label: 'filter incidents' },
|
|
387
|
-
{ keys: 'Enter', label: 'apply' },
|
|
388
|
-
{ keys: 'Esc', label: 'clear' },
|
|
389
|
-
]
|
|
390
|
-
: [
|
|
391
|
-
{ keys: '↑/↓', label: 'select incident' },
|
|
392
|
-
{ keys: 'Home/End', label: 'jump' },
|
|
393
|
-
{ keys: '/', label: 'filter' },
|
|
394
|
-
{ keys: 'x', label: 'export' },
|
|
395
|
-
{ keys: 'c', label: 'capture' },
|
|
396
|
-
...(selected.jumpLinks.length > 0 ? [{ keys: 'j', label: 'follow link' }] : []),
|
|
397
|
-
];
|
|
398
|
-
|
|
399
|
-
return this.renderList(width, height, {
|
|
400
|
-
title: 'Incident Review Workspace',
|
|
401
|
-
header: headerLines,
|
|
402
|
-
footer: footerLines,
|
|
403
|
-
hints,
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
}
|