@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.
Files changed (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. 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
- }