@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,470 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { createEmptyLine } from '../types/grid.ts';
3
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
- import type { UiControlPlaneSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
- import { truncateDisplay } from '../utils/terminal-width.ts';
6
- import type { ApprovalBroker, ControlPlaneRecentEvent, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
7
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
8
- import {
9
- type ConfirmState,
10
- handleConfirmInput,
11
- renderConfirmLines,
12
- } from './confirm-state.ts';
13
- import {
14
- buildDetailBlock,
15
- buildEmptyState,
16
- buildKeyValueLine,
17
- buildPanelLine,
18
- buildPanelWorkspace,
19
- DEFAULT_PANEL_PALETTE,
20
- type PanelPalette,
21
- } from './polish.ts';
22
-
23
- // Base chrome only — title band, state colors, and text tokens all come
24
- // straight from DEFAULT_PANEL_PALETTE (WO-002).
25
- const C = DEFAULT_PANEL_PALETTE;
26
-
27
- function formatTime(value?: number): string {
28
- if (!value) return 'n/a';
29
- return new Date(value).toLocaleString();
30
- }
31
-
32
- function connectionColor(state: string): string {
33
- if (state === 'connected' || state === 'healthy') return C.good;
34
- if (state === 'degraded' || state === 'connecting' || state === 'initializing') return C.warn;
35
- if (state === 'terminal_failure') return C.bad;
36
- return C.dim;
37
- }
38
-
39
- function approvalStatusColor(status: string): string {
40
- if (status === 'pending' || status === 'claimed') return C.warn;
41
- if (status === 'approved') return C.good;
42
- if (status === 'denied' || status === 'expired') return C.bad;
43
- return C.dim;
44
- }
45
-
46
- type ControlPlaneClient = UiControlPlaneSnapshot['clients'][number];
47
- type ControlPlaneApproval = UiControlPlaneSnapshot['approvals'][number];
48
- type ControlPlaneSession = UiControlPlaneSnapshot['sessions'][number];
49
- type ControlPlaneEvent = UiControlPlaneSnapshot['recentEvents'][number];
50
-
51
- type FocusSection = 'approvals' | 'clients' | 'sessions' | 'events';
52
-
53
- const FOCUS_SECTIONS: readonly FocusSection[] = ['approvals', 'clients', 'sessions', 'events'];
54
-
55
- type ControlPlaneItem =
56
- | { readonly kind: 'client'; readonly client: ControlPlaneClient }
57
- | { readonly kind: 'approval'; readonly approval: ControlPlaneApproval }
58
- | { readonly kind: 'session'; readonly session: ControlPlaneSession }
59
- | { readonly kind: 'event'; readonly event: ControlPlaneEvent };
60
-
61
- type ApprovalConfirmSubject = { readonly approvalId: string; readonly approved: boolean };
62
-
63
- /** Deps needed to drive real approve/deny and live session/event lookups (WO-121). */
64
- export interface ControlPlanePanelDeps {
65
- readonly approvalBroker?: ApprovalBroker;
66
- readonly sessionBroker?: SharedSessionBroker;
67
- readonly getControlPlaneRecentEvents?: (limit: number) => readonly ControlPlaneRecentEvent[];
68
- }
69
-
70
- /**
71
- * ControlPlanePanel — daemon control-plane console (WO-121).
72
- *
73
- * Tab cycles focus across four selectable sections (approvals, clients,
74
- * sessions, events); approvals is the primary/default section and supports
75
- * a=approve / d=deny via ApprovalBroker.resolveApproval behind ConfirmState.
76
- * Sessions and events pull from the live brokers when wired, falling back to
77
- * the read-model snapshot when they are not.
78
- */
79
- export class ControlPlanePanel extends ScrollableListPanel<ControlPlaneItem> {
80
- private readonly readModel?: UiReadModel<UiControlPlaneSnapshot>;
81
- private readonly approvalBroker?: ApprovalBroker;
82
- private readonly sessionBroker?: SharedSessionBroker;
83
- private readonly getControlPlaneRecentEvents?: (limit: number) => readonly ControlPlaneRecentEvent[];
84
- private readonly unsub: (() => void) | null;
85
- private focusSection: FocusSection = 'approvals';
86
- private confirmAction: ConfirmState<ApprovalConfirmSubject> | null = null;
87
-
88
- public constructor(
89
- readModel?: UiReadModel<UiControlPlaneSnapshot>,
90
- deps: ControlPlanePanelDeps = {},
91
- ) {
92
- super('control-plane', 'Control Plane', 'C', 'runtime-ops');
93
- this.showSelectionGutter = true; // I5: non-color selection affordance
94
- this.filterEnabled = true;
95
- this.filterLabel = 'Filter approvals';
96
- this.readModel = readModel;
97
- this.approvalBroker = deps.approvalBroker;
98
- this.sessionBroker = deps.sessionBroker;
99
- this.getControlPlaneRecentEvents = deps.getControlPlaneRecentEvents;
100
- this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
101
- }
102
-
103
- public override onDestroy(): void {
104
- this.unsub?.();
105
- }
106
-
107
- protected override getPalette(): PanelPalette {
108
- return C;
109
- }
110
-
111
- // -------------------------------------------------------------------------
112
- // Data
113
- // -------------------------------------------------------------------------
114
-
115
- /**
116
- * The read model's snapshot.approvals is capped upstream (SDK read model
117
- * calls approvalBroker.listApprovals(6)). Sourcing directly from the
118
- * broker when it's wired removes that cap so the panel can show and act
119
- * on the full pending set, not just the first six.
120
- */
121
- private _approvals(snapshot: UiControlPlaneSnapshot): readonly ControlPlaneApproval[] {
122
- const approvals = this.approvalBroker ? this.approvalBroker.listApprovals(50) : snapshot.approvals;
123
- const pending: ControlPlaneApproval[] = [];
124
- const rest: ControlPlaneApproval[] = [];
125
- for (const approval of approvals) {
126
- (approval.status === 'pending' ? pending : rest).push(approval);
127
- }
128
- return [...pending, ...rest];
129
- }
130
-
131
- private _sessions(snapshot: UiControlPlaneSnapshot): readonly ControlPlaneSession[] {
132
- return this.sessionBroker ? this.sessionBroker.listSessions(50) : snapshot.sessions;
133
- }
134
-
135
- private _recentEvents(snapshot: UiControlPlaneSnapshot): readonly ControlPlaneEvent[] {
136
- return this.getControlPlaneRecentEvents ? this.getControlPlaneRecentEvents(50) : snapshot.recentEvents;
137
- }
138
-
139
- protected getItems(): readonly ControlPlaneItem[] {
140
- if (!this.readModel) return [];
141
- const snapshot = this.readModel.getSnapshot();
142
- switch (this.focusSection) {
143
- case 'approvals':
144
- return this._approvals(snapshot).map((approval): ControlPlaneItem => ({ kind: 'approval', approval }));
145
- case 'sessions':
146
- return this._sessions(snapshot).map((session): ControlPlaneItem => ({ kind: 'session', session }));
147
- case 'events':
148
- return this._recentEvents(snapshot).map((event): ControlPlaneItem => ({ kind: 'event', event }));
149
- case 'clients':
150
- default:
151
- return snapshot.clients.map((client): ControlPlaneItem => ({ kind: 'client', client }));
152
- }
153
- }
154
-
155
- protected override filterMatches(item: ControlPlaneItem, q: string): boolean {
156
- switch (item.kind) {
157
- case 'client':
158
- return item.client.kind.toLowerCase().includes(q)
159
- || item.client.label.toLowerCase().includes(q)
160
- || item.client.transport.toLowerCase().includes(q);
161
- case 'approval':
162
- return item.approval.status.toLowerCase().includes(q)
163
- || item.approval.request.tool.toLowerCase().includes(q)
164
- || (item.approval.sessionId ?? '').toLowerCase().includes(q);
165
- case 'session':
166
- return item.session.status.toLowerCase().includes(q)
167
- || item.session.title.toLowerCase().includes(q);
168
- case 'event':
169
- return item.event.event.toLowerCase().includes(q);
170
- }
171
- }
172
-
173
- protected override getEmptyStateMessage(): string {
174
- switch (this.focusSection) {
175
- case 'approvals': return ' No approval pressure recorded.';
176
- case 'sessions': return ' No shared sessions recorded.';
177
- case 'events': return ' No recent control-plane events.';
178
- default: return ' No control-plane clients connected.';
179
- }
180
- }
181
-
182
- protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
183
- return [
184
- { command: '/cockpit', summary: 'watch operator posture from the terminal' },
185
- { command: '/schedule list', summary: 'run automation that creates surface and daemon traffic' },
186
- ];
187
- }
188
-
189
- // -------------------------------------------------------------------------
190
- // Input
191
- // -------------------------------------------------------------------------
192
-
193
- handleInput(key: string): boolean {
194
- if (this.lastError !== null) this.clearError();
195
-
196
- if (this.confirmAction) {
197
- const result = handleConfirmInput(this.confirmAction, key);
198
- if (result === 'confirmed') {
199
- this._executeConfirmed(this.confirmAction.subject);
200
- this.confirmAction = null;
201
- this.markDirty();
202
- return true;
203
- }
204
- if (result === 'cancelled') {
205
- this.confirmAction = null;
206
- this.markDirty();
207
- }
208
- return true;
209
- }
210
-
211
- if (!this.filterActive) {
212
- if (key === 'tab') {
213
- this._cycleFocus();
214
- return true;
215
- }
216
- if (this.focusSection === 'approvals' && this.approvalBroker && (key === 'a' || key === 'd')) {
217
- this._beginApprovalDecision(key === 'a');
218
- return true;
219
- }
220
- }
221
-
222
- return super.handleInput(key);
223
- }
224
-
225
- private _cycleFocus(): void {
226
- const idx = FOCUS_SECTIONS.indexOf(this.focusSection);
227
- this.focusSection = FOCUS_SECTIONS[(idx + 1) % FOCUS_SECTIONS.length]!;
228
- this.filterLabel = `Filter ${this.focusSection}`;
229
- this.selectedIndex = 0;
230
- this.markDirty();
231
- }
232
-
233
- private _beginApprovalDecision(approved: boolean): void {
234
- const item = this.getSelectedItem();
235
- if (!item || item.kind !== 'approval') return;
236
- if (item.approval.status !== 'pending' && item.approval.status !== 'claimed') return;
237
- this.confirmAction = {
238
- subject: { approvalId: item.approval.id, approved },
239
- label: `${item.approval.request.tool} · ${truncateDisplay(item.approval.sessionId ?? item.approval.id, 24)}`,
240
- verb: approved ? 'Approve' : 'Deny',
241
- };
242
- this.markDirty();
243
- }
244
-
245
- private _executeConfirmed(subject: ApprovalConfirmSubject): void {
246
- if (!this.approvalBroker) return;
247
- void this.approvalBroker.resolveApproval(subject.approvalId, {
248
- approved: subject.approved,
249
- actor: 'operator',
250
- actorSurface: 'tui',
251
- }).catch((err) => {
252
- this.setError(`${subject.approved ? 'Approve' : 'Deny'} failed: ${summarizeError(err)}`);
253
- this.markDirty();
254
- });
255
- }
256
-
257
- // -------------------------------------------------------------------------
258
- // Rendering — item rows
259
- // -------------------------------------------------------------------------
260
-
261
- protected renderItem(item: ControlPlaneItem, _index: number, selected: boolean, width: number): Line {
262
- switch (item.kind) {
263
- case 'client': return this._renderClientRow(item.client, selected, width);
264
- case 'approval': return this._renderApprovalRow(item.approval, selected, width);
265
- case 'session': return this._renderSessionRow(item.session, selected, width);
266
- case 'event': return this._renderEventRow(item.event, selected, width);
267
- }
268
- }
269
-
270
- private _renderClientRow(client: ControlPlaneClient, selected: boolean, width: number): Line {
271
- const bg = selected ? C.selectBg : undefined;
272
- return buildPanelLine(width, [
273
- [' ', C.label, bg],
274
- [client.kind.padEnd(10), C.info, bg],
275
- [` ${truncateDisplay(client.label, 20).padEnd(20)}`, C.value, bg],
276
- [` ${client.transport.padEnd(12)}`, C.dim, bg],
277
- [` ${truncateDisplay(formatTime(client.lastSeenAt), Math.max(0, width - 46))}`, C.dim, bg],
278
- ]);
279
- }
280
-
281
- private _renderApprovalRow(approval: ControlPlaneApproval, selected: boolean, width: number): Line {
282
- const bg = selected ? C.selectBg : undefined;
283
- return buildPanelLine(width, [
284
- [' ', C.label, bg],
285
- [approval.status.padEnd(10), approvalStatusColor(approval.status), bg],
286
- [` ${truncateDisplay(approval.request.tool, 16).padEnd(16)}`, C.value, bg],
287
- [` ${truncateDisplay(approval.sessionId ?? approval.id, Math.max(0, width - 30))}`, C.dim, bg],
288
- ]);
289
- }
290
-
291
- private _renderSessionRow(session: ControlPlaneSession, selected: boolean, width: number): Line {
292
- const bg = selected ? C.selectBg : undefined;
293
- return buildPanelLine(width, [
294
- [' ', C.label, bg],
295
- [session.status.padEnd(10), session.status === 'active' ? C.good : C.dim, bg],
296
- [` ${truncateDisplay(session.title, 20).padEnd(20)}`, C.value, bg],
297
- [` ${truncateDisplay(session.activeAgentId ?? session.id, Math.max(0, width - 34))}`, C.dim, bg],
298
- ]);
299
- }
300
-
301
- private _renderEventRow(event: ControlPlaneEvent, selected: boolean, width: number): Line {
302
- const bg = selected ? C.selectBg : undefined;
303
- const payload = typeof event.payload === 'string' ? event.payload : (JSON.stringify(event.payload) ?? '');
304
- return buildPanelLine(width, [
305
- [' ', C.label, bg],
306
- [truncateDisplay(event.event, 16).padEnd(16), C.info, bg],
307
- [` ${truncateDisplay(payload, Math.max(0, width - 19))}`, C.dim, bg],
308
- ]);
309
- }
310
-
311
- // -------------------------------------------------------------------------
312
- // Rendering — workspace
313
- // -------------------------------------------------------------------------
314
-
315
- public render(width: number, height: number): Line[] {
316
- const intro = 'Shared daemon control plane state, live clients, approval pressure, and recent omnichannel session posture.';
317
-
318
- if (!this.readModel) {
319
- const workspace = buildPanelWorkspace(width, height, {
320
- title: 'Control Plane',
321
- intro,
322
- sections: [{
323
- lines: buildEmptyState(
324
- width,
325
- ' Runtime read model not wired.',
326
- 'This panel needs the shared control-plane read model to inspect clients, requests, and approvals.',
327
- [{ command: '/cockpit', summary: 'use the cockpit while control-plane wiring is unavailable' }],
328
- C,
329
- ),
330
- }],
331
- palette: C,
332
- });
333
- while (workspace.length < height) workspace.push(createEmptyLine(width));
334
- return workspace;
335
- }
336
-
337
- const snapshot = this.readModel.getSnapshot();
338
- const approvals = this._approvals(snapshot);
339
- const sessions = this._sessions(snapshot);
340
- const recentEvents = this._recentEvents(snapshot);
341
- const clients = snapshot.clients;
342
- const pendingApprovals = approvals.filter((entry) => entry.status === 'pending').length;
343
-
344
- const headerLines: Line[] = [
345
- buildKeyValueLine(width, [
346
- { label: 'state', value: snapshot.connectionState, valueColor: connectionColor(snapshot.connectionState) },
347
- { label: 'clients', value: String(snapshot.activeClientIds.length), valueColor: snapshot.activeClientIds.length > 0 ? C.good : C.dim },
348
- { label: 'requests', value: String(snapshot.requestCount), valueColor: snapshot.requestCount > 0 ? C.info : C.dim },
349
- { label: 'errors', value: String(snapshot.errorCount), valueColor: snapshot.errorCount > 0 ? C.bad : C.dim },
350
- ], C),
351
- buildKeyValueLine(width, [
352
- { label: 'host', value: `${snapshot.host}:${snapshot.port}`, valueColor: C.value },
353
- { label: 'approvals', value: `${pendingApprovals} pending / ${approvals.length}`, valueColor: pendingApprovals > 0 ? C.warn : C.dim },
354
- { label: 'sessions', value: String(sessions.length), valueColor: sessions.length > 0 ? C.info : C.dim },
355
- { label: 'events', value: String(recentEvents.length), valueColor: recentEvents.length > 0 ? C.info : C.dim },
356
- ], C),
357
- this._buildFocusTabsLine(width),
358
- ];
359
-
360
- if (clients.length === 0 && approvals.length === 0 && sessions.length === 0 && recentEvents.length === 0) {
361
- return this.renderList(width, height, {
362
- title: 'Control Plane',
363
- header: headerLines,
364
- emptyMessage: ' No control-plane activity recorded.',
365
- });
366
- }
367
-
368
- this.clampSelection();
369
- const selected = this.getSelectedItem();
370
-
371
- const footerLines: Line[] = [];
372
- if (selected) {
373
- footerLines.push(...this._buildDetailLines(width, selected));
374
- if (this.confirmAction) footerLines.push(...renderConfirmLines(width, this.confirmAction));
375
- } else {
376
- footerLines.push(buildPanelLine(width, [[' Nothing selected in this section.', C.dim]]));
377
- }
378
-
379
- return this.renderList(width, height, {
380
- title: 'Control Plane',
381
- header: headerLines,
382
- footer: footerLines,
383
- hints: this._buildHints(selected),
384
- });
385
- }
386
-
387
- private _buildFocusTabsLine(width: number): Line {
388
- const seg = (label: string, section: FocusSection): [string, string] => [
389
- ` ${label} `,
390
- this.focusSection === section ? C.value : C.dim,
391
- ];
392
- return buildPanelLine(width, [
393
- seg('Approvals', 'approvals'),
394
- seg('Clients', 'clients'),
395
- seg('Sessions', 'sessions'),
396
- seg('Events', 'events'),
397
- [' (Tab to switch)', C.dim],
398
- ]);
399
- }
400
-
401
- private _buildHints(selected: ControlPlaneItem | undefined): ReadonlyArray<{ keys: string; label: string }> {
402
- if (this.filterActive) {
403
- return [
404
- { keys: 'type', label: `filter ${this.focusSection}` },
405
- { keys: 'Enter', label: 'apply' },
406
- { keys: 'Esc', label: 'clear' },
407
- ];
408
- }
409
- const hints: Array<{ keys: string; label: string }> = [
410
- { keys: 'Up/Down', label: `select ${this.focusSection.slice(0, -1)}` },
411
- { keys: 'Tab', label: 'section' },
412
- ];
413
- if (
414
- this.focusSection === 'approvals'
415
- && this.approvalBroker
416
- && selected?.kind === 'approval'
417
- && (selected.approval.status === 'pending' || selected.approval.status === 'claimed')
418
- ) {
419
- hints.push({ keys: 'a', label: 'approve' }, { keys: 'd', label: 'deny' });
420
- }
421
- hints.push({ keys: '/', label: 'filter' });
422
- return hints;
423
- }
424
-
425
- private _buildDetailLines(width: number, item: ControlPlaneItem): Line[] {
426
- if (item.kind === 'client') {
427
- const client = item.client;
428
- const rows: Line[] = [
429
- buildPanelLine(width, [[' Client: ', C.label], [client.label, C.value], [' Kind: ', C.label], [client.kind, C.info]]),
430
- buildPanelLine(width, [[' Transport: ', C.label], [client.transport, C.value], [' Connected: ', C.label], [client.connected ? 'yes' : 'no', client.connected ? C.good : C.warn]]),
431
- buildPanelLine(width, [[' Route: ', C.label], [client.routeId ?? 'n/a', C.dim], [' Session: ', C.label], [client.sessionId ?? 'n/a', C.dim]]),
432
- buildPanelLine(width, [[' Last seen: ', C.label], [formatTime(client.lastSeenAt), C.dim], [' Remote: ', C.label], [truncateDisplay(client.remoteAddress ?? 'n/a', Math.max(0, width - 36)), C.dim]]),
433
- ];
434
- return buildDetailBlock(width, `Client · ${client.label}`, rows, C);
435
- }
436
-
437
- if (item.kind === 'approval') {
438
- const approval = item.approval;
439
- const rows: Line[] = [
440
- buildPanelLine(width, [[' Approval: ', C.label], [approval.id, C.value], [' Status: ', C.label], [approval.status, approvalStatusColor(approval.status)]]),
441
- buildPanelLine(width, [[' Tool: ', C.label], [approval.request.tool, C.info], [' Risk: ', C.label], [approval.request.analysis.riskLevel, C.warn]]),
442
- buildPanelLine(width, [[' Session: ', C.label], [approval.sessionId ?? 'n/a', C.dim], [' Route: ', C.label], [approval.routeId ?? 'n/a', C.dim]]),
443
- buildPanelLine(width, [[' Requested: ', C.label], [formatTime(approval.createdAt), C.dim], [' Updated: ', C.label], [formatTime(approval.updatedAt), C.dim]]),
444
- ];
445
- if (approval.decision) {
446
- rows.push(buildPanelLine(width, [[' Decision: ', C.label], [approval.decision.approved ? 'approved' : 'denied', approval.decision.approved ? C.good : C.bad]]));
447
- }
448
- return buildDetailBlock(width, `Approval · ${approval.request.tool}`, rows, C);
449
- }
450
-
451
- if (item.kind === 'session') {
452
- const session = item.session;
453
- const rows: Line[] = [
454
- buildPanelLine(width, [[' Session: ', C.label], [session.title, C.value], [' Status: ', C.label], [session.status, session.status === 'active' ? C.good : C.dim]]),
455
- buildPanelLine(width, [[' Kind: ', C.label], [session.kind, C.info], [' Messages: ', C.label], [String(session.messageCount), C.value]]),
456
- buildPanelLine(width, [[' Active agent: ', C.label], [session.activeAgentId ?? 'none (unattached)', session.activeAgentId ? C.good : C.dim], [' Last agent: ', C.label], [session.lastAgentId ?? 'n/a', C.dim]]),
457
- buildPanelLine(width, [[' Pending inputs: ', C.label], [String(session.pendingInputCount), session.pendingInputCount > 0 ? C.warn : C.dim], [' Last activity: ', C.label], [formatTime(session.lastActivityAt), C.dim]]),
458
- ];
459
- return buildDetailBlock(width, `Session · ${session.title}`, rows, C);
460
- }
461
-
462
- const event = item.event;
463
- const payload = typeof event.payload === 'string' ? event.payload : (JSON.stringify(event.payload) ?? '');
464
- const rows: Line[] = [
465
- buildPanelLine(width, [[' Event: ', C.label], [event.event, C.info], [' At: ', C.label], [formatTime(event.createdAt), C.dim]]),
466
- buildPanelLine(width, [[' Payload: ', C.label], [truncateDisplay(payload, Math.max(0, width - 12)), C.dim]]),
467
- ];
468
- return buildDetailBlock(width, `Event · ${event.event}`, rows, C);
469
- }
470
- }