@pellux/goodvibes-tui 1.0.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.
Files changed (209) hide show
  1. package/CHANGELOG.md +130 -127
  2. package/README.md +32 -14
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
  6. package/src/cli/entrypoint.ts +2 -0
  7. package/src/core/alert-gating.ts +67 -0
  8. package/src/core/approval-alert.ts +72 -0
  9. package/src/core/budget-breach-notifier.ts +106 -0
  10. package/src/core/conversation-rendering.ts +19 -0
  11. package/src/core/conversation.ts +58 -0
  12. package/src/core/focus-tracker.ts +41 -0
  13. package/src/core/long-task-notifier.ts +33 -6
  14. package/src/core/stream-event-wiring.ts +104 -2
  15. package/src/core/stream-stall-watchdog.ts +41 -17
  16. package/src/core/system-message-router.ts +37 -51
  17. package/src/core/turn-cancellation.ts +20 -0
  18. package/src/core/turn-event-wiring.ts +64 -3
  19. package/src/export/cost-utils.ts +126 -8
  20. package/src/input/command-registry.ts +54 -1
  21. package/src/input/commands/checkpoint-runtime.ts +280 -0
  22. package/src/input/commands/codebase-runtime.ts +192 -0
  23. package/src/input/commands/diff-runtime.ts +61 -30
  24. package/src/input/commands/eval.ts +1 -1
  25. package/src/input/commands/health-runtime.ts +1 -1
  26. package/src/input/commands/image-runtime.ts +112 -0
  27. package/src/input/commands/intelligence-runtime.ts +11 -1
  28. package/src/input/commands/local-auth-runtime.ts +4 -1
  29. package/src/input/commands/local-runtime.ts +9 -3
  30. package/src/input/commands/marketplace-runtime.ts +2 -2
  31. package/src/input/commands/memory.ts +6 -1
  32. package/src/input/commands/operator-panel-runtime.ts +40 -24
  33. package/src/input/commands/planning-runtime.ts +26 -3
  34. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  35. package/src/input/commands/plugin-runtime.ts +2 -2
  36. package/src/input/commands/policy-dispatch.ts +21 -0
  37. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  38. package/src/input/commands/qrcode-runtime.ts +3 -3
  39. package/src/input/commands/recall-review.ts +35 -0
  40. package/src/input/commands/remote-runtime.ts +3 -3
  41. package/src/input/commands/runtime-services.ts +54 -13
  42. package/src/input/commands/services-runtime.ts +1 -1
  43. package/src/input/commands/session-content.ts +12 -0
  44. package/src/input/commands/session-workflow.ts +19 -1
  45. package/src/input/commands/settings-sync-runtime.ts +1 -1
  46. package/src/input/commands/share-runtime.ts +9 -2
  47. package/src/input/commands/shell-core.ts +15 -7
  48. package/src/input/commands/skills-runtime.ts +2 -8
  49. package/src/input/commands/subscription-runtime.ts +2 -2
  50. package/src/input/commands/test-runtime.ts +277 -0
  51. package/src/input/commands/websearch-runtime.ts +101 -0
  52. package/src/input/commands/work-plan-runtime.ts +36 -10
  53. package/src/input/commands/workstream-runtime.ts +338 -0
  54. package/src/input/commands.ts +12 -0
  55. package/src/input/config-modal-types.ts +161 -0
  56. package/src/input/config-modal.ts +377 -0
  57. package/src/input/feed-context-factory.ts +7 -8
  58. package/src/input/handler-command-route.ts +27 -17
  59. package/src/input/handler-content-actions.ts +22 -8
  60. package/src/input/handler-feed-routes.ts +107 -7
  61. package/src/input/handler-feed.ts +49 -13
  62. package/src/input/handler-interactions.ts +3 -3
  63. package/src/input/handler-modal-routes.ts +65 -0
  64. package/src/input/handler-modal-stack.ts +3 -5
  65. package/src/input/handler-modal-token-routes.ts +16 -49
  66. package/src/input/handler-picker-routes.ts +11 -94
  67. package/src/input/handler-shortcuts.ts +24 -14
  68. package/src/input/handler-types.ts +3 -6
  69. package/src/input/handler-ui-state.ts +10 -22
  70. package/src/input/handler.ts +10 -28
  71. package/src/input/keybindings.ts +22 -8
  72. package/src/input/model-picker-types.ts +24 -6
  73. package/src/input/model-picker.ts +58 -0
  74. package/src/input/panel-integration-actions.ts +9 -170
  75. package/src/input/settings-modal-data.ts +95 -0
  76. package/src/input/settings-modal-mutations.ts +6 -4
  77. package/src/main.ts +41 -44
  78. package/src/panels/agent-inspector-shared.ts +35 -11
  79. package/src/panels/base-panel.ts +22 -1
  80. package/src/panels/builtin/agent.ts +21 -107
  81. package/src/panels/builtin/development.ts +16 -62
  82. package/src/panels/builtin/knowledge.ts +8 -32
  83. package/src/panels/builtin/operations.ts +84 -428
  84. package/src/panels/builtin/session.ts +21 -112
  85. package/src/panels/builtin/shared.ts +9 -43
  86. package/src/panels/builtin-modals.ts +218 -0
  87. package/src/panels/builtin-panels.ts +5 -0
  88. package/src/panels/cost-tracker-panel.ts +63 -14
  89. package/src/panels/diff-panel.ts +123 -60
  90. package/src/panels/eval-registry.ts +60 -0
  91. package/src/panels/fleet-panel.ts +800 -0
  92. package/src/panels/fleet-read-model.ts +477 -0
  93. package/src/panels/fleet-steer.ts +92 -0
  94. package/src/panels/fleet-stop.ts +125 -0
  95. package/src/panels/fleet-tabs.ts +230 -0
  96. package/src/panels/fleet-transcript.ts +356 -0
  97. package/src/panels/git-panel.ts +9 -9
  98. package/src/panels/index.ts +7 -31
  99. package/src/panels/local-auth-panel.ts +10 -0
  100. package/src/panels/modals/hooks-modal.ts +187 -0
  101. package/src/panels/modals/keybindings-modal.ts +151 -0
  102. package/src/panels/modals/knowledge-modal.ts +158 -0
  103. package/src/panels/modals/local-auth-modal.ts +132 -0
  104. package/src/panels/modals/marketplace-modal.ts +218 -0
  105. package/src/panels/modals/memory-modal.ts +180 -0
  106. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  107. package/src/panels/modals/modal-theme.ts +36 -0
  108. package/src/panels/modals/pairing-modal.ts +144 -0
  109. package/src/panels/modals/planning-modal.ts +282 -0
  110. package/src/panels/modals/plugins-modal.ts +174 -0
  111. package/src/panels/modals/policy-modal.ts +256 -0
  112. package/src/panels/modals/provider-health-modal.ts +136 -0
  113. package/src/panels/modals/remote-modal.ts +115 -0
  114. package/src/panels/modals/sandbox-modal.ts +150 -0
  115. package/src/panels/modals/security-modal.ts +217 -0
  116. package/src/panels/modals/services-modal.ts +179 -0
  117. package/src/panels/modals/settings-sync-modal.ts +142 -0
  118. package/src/panels/modals/skills-modal.ts +189 -0
  119. package/src/panels/modals/subscription-modal.ts +172 -0
  120. package/src/panels/modals/work-plan-modal.ts +149 -0
  121. package/src/panels/panel-confirm-overlay.ts +72 -0
  122. package/src/panels/panel-manager.ts +108 -5
  123. package/src/panels/project-planning-answer-actions.ts +4 -2
  124. package/src/panels/scrollable-list-panel.ts +9 -0
  125. package/src/panels/skills-panel.ts +7 -51
  126. package/src/panels/token-budget-panel.ts +5 -3
  127. package/src/panels/types.ts +26 -0
  128. package/src/permissions/hunk-selection.ts +179 -0
  129. package/src/permissions/prompt.ts +60 -4
  130. package/src/renderer/compaction-history-modal.ts +19 -3
  131. package/src/renderer/compaction-preview.ts +13 -2
  132. package/src/renderer/compaction-quality.ts +100 -0
  133. package/src/renderer/config-modal.ts +81 -0
  134. package/src/renderer/conversation-overlays.ts +10 -17
  135. package/src/renderer/fleet-tab-strip.ts +56 -0
  136. package/src/renderer/footer-tips.ts +10 -2
  137. package/src/renderer/git-status.ts +40 -0
  138. package/src/renderer/help-overlay.ts +2 -2
  139. package/src/renderer/model-workspace.ts +44 -1
  140. package/src/renderer/panel-workspace-bar.ts +8 -2
  141. package/src/renderer/shell-surface.ts +8 -1
  142. package/src/renderer/turn-injection.ts +114 -0
  143. package/src/renderer/ui-factory.ts +91 -7
  144. package/src/runtime/bootstrap-command-context.ts +24 -1
  145. package/src/runtime/bootstrap-command-parts.ts +36 -7
  146. package/src/runtime/bootstrap-core.ts +19 -4
  147. package/src/runtime/bootstrap-hook-bridge.ts +5 -0
  148. package/src/runtime/bootstrap-shell.ts +41 -17
  149. package/src/runtime/bootstrap.ts +11 -17
  150. package/src/runtime/code-index-services.ts +112 -0
  151. package/src/runtime/orchestrator-core-services.ts +49 -0
  152. package/src/runtime/process-lifecycle.ts +3 -1
  153. package/src/runtime/services.ts +91 -43
  154. package/src/runtime/ui-services.ts +8 -0
  155. package/src/runtime/workstream-services.ts +195 -0
  156. package/src/shell/blocking-input.ts +22 -1
  157. package/src/shell/ui-openers.ts +129 -17
  158. package/src/utils/format-duration.ts +8 -18
  159. package/src/utils/splash-lines.ts +1 -1
  160. package/src/version.ts +1 -1
  161. package/src/panels/agent-inspector-panel.ts +0 -786
  162. package/src/panels/approval-panel.ts +0 -252
  163. package/src/panels/automation-control-panel.ts +0 -479
  164. package/src/panels/cockpit-panel.ts +0 -481
  165. package/src/panels/cockpit-read-model.ts +0 -233
  166. package/src/panels/communication-panel.ts +0 -256
  167. package/src/panels/control-plane-panel.ts +0 -470
  168. package/src/panels/debug-panel.ts +0 -609
  169. package/src/panels/docs-panel.ts +0 -375
  170. package/src/panels/eval-panel.ts +0 -627
  171. package/src/panels/file-explorer-panel.ts +0 -664
  172. package/src/panels/file-preview-panel.ts +0 -517
  173. package/src/panels/hooks-panel.ts +0 -401
  174. package/src/panels/incident-review-panel.ts +0 -406
  175. package/src/panels/intelligence-panel.ts +0 -383
  176. package/src/panels/knowledge-graph-panel.ts +0 -506
  177. package/src/panels/marketplace-panel.ts +0 -399
  178. package/src/panels/memory-panel.ts +0 -558
  179. package/src/panels/ops-control-panel.ts +0 -329
  180. package/src/panels/ops-strategy-panel.ts +0 -321
  181. package/src/panels/orchestration-panel.ts +0 -465
  182. package/src/panels/panel-list-panel.ts +0 -557
  183. package/src/panels/plan-dashboard-panel.ts +0 -707
  184. package/src/panels/policy-panel.ts +0 -517
  185. package/src/panels/project-planning-panel.ts +0 -721
  186. package/src/panels/provider-health-panel.ts +0 -678
  187. package/src/panels/provider-health-tracker.ts +0 -306
  188. package/src/panels/provider-health-views.ts +0 -560
  189. package/src/panels/qr-panel.ts +0 -280
  190. package/src/panels/remote-panel.ts +0 -534
  191. package/src/panels/routes-panel.ts +0 -241
  192. package/src/panels/sandbox-panel.ts +0 -456
  193. package/src/panels/security-panel.ts +0 -447
  194. package/src/panels/services-panel.ts +0 -329
  195. package/src/panels/session-browser-panel.ts +0 -487
  196. package/src/panels/settings-sync-panel.ts +0 -398
  197. package/src/panels/subscription-panel.ts +0 -342
  198. package/src/panels/symbol-outline-panel.ts +0 -619
  199. package/src/panels/system-messages-panel.ts +0 -364
  200. package/src/panels/tasks-panel.ts +0 -608
  201. package/src/panels/thinking-panel.ts +0 -333
  202. package/src/panels/tool-inspector-panel.ts +0 -537
  203. package/src/panels/work-plan-panel.ts +0 -530
  204. package/src/panels/worktree-panel.ts +0 -360
  205. package/src/panels/wrfc-panel.ts +0 -790
  206. package/src/renderer/agent-detail-modal.ts +0 -465
  207. package/src/renderer/live-tail-modal.ts +0 -156
  208. package/src/renderer/process-modal.ts +0 -656
  209. 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
- }