@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,534 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { createEmptyLine } from '../types/grid.ts';
3
- import { BasePanel } from './base-panel.ts';
4
- import type { PanelIntegrationContext } from './types.ts';
5
- import type { UiReadModel, UiRemoteSnapshot } from '../runtime/ui-read-models.ts';
6
- import {
7
- buildDetailBlock,
8
- buildEmptyState,
9
- buildPanelListRow,
10
- buildPanelLine,
11
- buildSummaryBlock,
12
- buildPanelWorkspace,
13
- DEFAULT_PANEL_PALETTE,
14
- resolvePrimaryScrollableSection,
15
- type PanelWorkspaceSection,
16
- } from './polish.ts';
17
- import { truncateDisplay } from '../utils/terminal-width.ts';
18
- import { getTrackedVisibleWindow } from '../renderer/surface-layout.ts';
19
-
20
- // Base chrome only — title band, state colors, and text tokens all come
21
- // straight from DEFAULT_PANEL_PALETTE (WO-002).
22
- const C = DEFAULT_PANEL_PALETTE;
23
-
24
- function stateColor(state: string): string {
25
- switch (state) {
26
- case 'connected':
27
- case 'syncing':
28
- return C.good;
29
- case 'degraded':
30
- case 'reconnecting':
31
- case 'authenticating':
32
- case 'initializing':
33
- return C.warn;
34
- case 'terminal_failure':
35
- return C.bad;
36
- default:
37
- return C.dim;
38
- }
39
- }
40
-
41
- function formatTimestamp(value?: number): string {
42
- return value ? new Date(value).toLocaleTimeString() : 'n/a';
43
- }
44
-
45
- function truncate(text: string, width: number): string {
46
- return truncateDisplay(text, width);
47
- }
48
-
49
- // Splits a machine-generated slash-command string (e.g. `/remote show foo`)
50
- // into the { name, args } shape ctx.executeCommand expects — the same
51
- // leading-slash-stripping approach panel-integration-actions.ts uses for
52
- // ApprovalPanel's dispatched command.
53
- function parseCommand(command: string): { name: string; args: string[] } | null {
54
- const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
55
- const [name, ...args] = parts;
56
- return name ? { name, args } : null;
57
- }
58
-
59
- // Mirrors RemoteSupervisor's own stateIsDegraded()/degradedConnections
60
- // definition (sdk/platform/runtime/remote/supervisor.js) so "degraded/stale"
61
- // means the same thing here as it does in the posture summary above.
62
- function isSupervisorEntryDegraded(entry: { transportState: string; heartbeat: { status: string } }): boolean {
63
- const degradedTransport = entry.transportState === 'degraded'
64
- || entry.transportState === 'reconnecting'
65
- || entry.transportState === 'terminal_failure';
66
- return degradedTransport || entry.heartbeat.status !== 'fresh';
67
- }
68
-
69
- export class RemotePanel extends BasePanel {
70
- private readonly readModel?: UiReadModel<UiRemoteSnapshot>;
71
- private readonly unsub: (() => void) | null;
72
- private selectedIndex = 0;
73
- private scrollOffset = 0;
74
- private browseMode: 'connections' | 'contracts' = 'connections';
75
- // Set by handleInput (enter/r) and consumed on the very next
76
- // handlePanelIntegrationAction dispatch of that same key, mirroring the
77
- // token-budget-panel pattern — handleInput has no ctx.executeCommand.
78
- private pendingCommand: { name: string; args: string[] } | null = null;
79
-
80
- public constructor(readModel?: UiReadModel<UiRemoteSnapshot>) {
81
- super('remote', 'Remote', '▰', 'providers');
82
- this.readModel = readModel;
83
- this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
84
- }
85
-
86
- public override onDestroy(): void {
87
- this.unsub?.();
88
- }
89
-
90
- public handleInput(key: string): boolean {
91
- // r = dispatch /remote recover for whatever is currently selected (or
92
- // the runtime's first non-fresh session when nothing is selected).
93
- if (this.readModel && key === 'r') {
94
- const runnerId = this.getSelectedRunnerId();
95
- const parsed = parseCommand(runnerId ? `/remote recover ${runnerId}` : '/remote recover');
96
- if (parsed) {
97
- this.pendingCommand = parsed;
98
- return true;
99
- }
100
- }
101
- // Enter on a degraded/stale supervisor selection dispatches its
102
- // top recovery action (already machine-readable via .recovery[0].command)
103
- // instead of requiring the operator to retype it.
104
- if (this.readModel && (key === 'enter' || key === 'return')) {
105
- const entry = this.getSelectedSupervisorEntry();
106
- if (entry && isSupervisorEntryDegraded(entry) && entry.recovery.length > 0) {
107
- const parsed = parseCommand(entry.recovery[0].command);
108
- if (parsed) {
109
- this.pendingCommand = parsed;
110
- return true;
111
- }
112
- }
113
- }
114
- const activeConnections = this.getActiveConnections();
115
- const contracts = this.readModel?.getSnapshot().contracts ?? [];
116
- const browseCount = this.browseMode === 'connections' && activeConnections.length > 0
117
- ? activeConnections.length
118
- : contracts.length;
119
- if (key === 'tab' && contracts.length > 0) {
120
- this.browseMode = this.browseMode === 'connections' ? 'contracts' : 'connections';
121
- this.selectedIndex = 0;
122
- this.markDirty();
123
- return true;
124
- }
125
- if (browseCount === 0) return false;
126
- if (key === 'up' || key === 'k') {
127
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
128
- this.markDirty();
129
- return true;
130
- }
131
- if (key === 'down' || key === 'j') {
132
- this.selectedIndex = Math.min(browseCount - 1, this.selectedIndex + 1);
133
- this.markDirty();
134
- return true;
135
- }
136
- if (key === 'home') {
137
- this.selectedIndex = 0;
138
- this.markDirty();
139
- return true;
140
- }
141
- if (key === 'end') {
142
- this.selectedIndex = Math.max(0, browseCount - 1);
143
- this.markDirty();
144
- return true;
145
- }
146
- return false;
147
- }
148
-
149
- private getActiveConnections() {
150
- return this.readModel?.getSnapshot().acp.activeConnections ?? [];
151
- }
152
-
153
- // Runner id backing the currently browsed row, whichever browse mode is
154
- // active — shared by the Enter/r dispatch logic and render()'s own
155
- // selection lookups so they never drift apart.
156
- private getSelectedRunnerId(): string | null {
157
- const snapshot = this.readModel?.getSnapshot();
158
- if (!snapshot) return null;
159
- const activeConnections = this.getActiveConnections();
160
- const contracts = snapshot.contracts;
161
- const viewingConnections = this.browseMode === 'connections' && activeConnections.length > 0;
162
- // selectedIndex indexes only the mode's active list (activeConnections when
163
- // viewing connections, otherwise contracts); .at() keeps that read off any
164
- // raw source array (no-raw-selectedindex-read rule).
165
- if (viewingConnections) {
166
- return activeConnections.at(this.selectedIndex)?.agentId ?? null;
167
- }
168
- return contracts.at(this.selectedIndex)?.runnerId ?? null;
169
- }
170
-
171
- private getSelectedSupervisorEntry() {
172
- const snapshot = this.readModel?.getSnapshot();
173
- if (!snapshot) return null;
174
- const runnerId = this.getSelectedRunnerId();
175
- if (!runnerId) return null;
176
- return snapshot.supervisor.sessions.find((entry) => entry.runnerId === runnerId) ?? null;
177
- }
178
-
179
- public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
180
- if (!this.pendingCommand) return false;
181
- const { name, args } = this.pendingCommand;
182
- this.pendingCommand = null;
183
- void ctx.executeCommand?.(name, args);
184
- return true;
185
- }
186
-
187
- public render(width: number, height: number): Line[] {
188
- this.needsRender = false;
189
- const intro = 'Bridge, ACP, runner-contract, and artifact posture for self-hosted remote work.';
190
-
191
- if (!this.readModel) {
192
- const sectionLines = buildEmptyState(
193
- width,
194
- ' Runtime store not wired into this panel yet.',
195
- 'The remote control room needs the shell read model so it can display ACP state, runner contracts, and replay artifacts.',
196
- [
197
- { command: '/remote setup', summary: 'review bootstrap, env, tunnel, and bridge guidance' },
198
- { command: '/remote panel', summary: 'reopen the panel from the shell-owned runtime' },
199
- ],
200
- C,
201
- );
202
- const lines = buildPanelWorkspace(width, height, {
203
- title: 'Remote Control Room',
204
- intro,
205
- sections: [{ lines: sectionLines }],
206
- palette: C,
207
- });
208
- while (lines.length < height) lines.push(createEmptyLine(width));
209
- return lines;
210
- }
211
-
212
- const snapshot = this.readModel.getSnapshot();
213
- const daemon = snapshot.daemon;
214
- const acp = snapshot.acp;
215
- const activeConnections = this.getActiveConnections();
216
- const artifactCount = snapshot.artifacts.length;
217
- const pools = snapshot.pools;
218
- const contracts = snapshot.contracts;
219
- const supervisor = snapshot.supervisor;
220
- const distributed = {
221
- pairRequests: { pending: snapshot.distributed.pairRequests.length },
222
- peers: {
223
- total: snapshot.distributed.peers.length,
224
- connected: snapshot.distributed.peers.filter((peer) => peer.status === 'connected').length,
225
- nodes: snapshot.distributed.peers.filter((peer) => peer.kind === 'node').length,
226
- devices: snapshot.distributed.peers.filter((peer) => peer.kind === 'device').length,
227
- },
228
- work: {
229
- queued: snapshot.distributed.work.filter((item) => item.status === 'queued').length,
230
- claimed: snapshot.distributed.work.filter((item) => item.status === 'claimed').length,
231
- },
232
- };
233
-
234
- const postureLines: Line[] = [
235
- buildPanelLine(width, [
236
- [' daemon ', C.label],
237
- [daemon.transportState.toUpperCase(), stateColor(daemon.transportState)],
238
- [' running ', C.label],
239
- [daemon.isRunning ? 'yes' : 'no', daemon.isRunning ? C.good : C.dim],
240
- [' reconnects ', C.label],
241
- [String(daemon.reconnectAttempts), daemon.reconnectAttempts > 0 ? C.warn : C.good],
242
- [' jobs ', C.label],
243
- [String(daemon.runningJobCount), daemon.runningJobCount > 0 ? C.info : C.dim],
244
- ]),
245
- buildPanelLine(width, [
246
- [' ACP manager ', C.label],
247
- [acp.transportState.toUpperCase(), stateColor(acp.transportState)],
248
- [' active connections ', C.label],
249
- [String(acp.activeConnections.length), acp.activeConnections.length > 0 ? C.info : C.dim],
250
- [' total messages ', C.label],
251
- [String(acp.totalMessages), acp.totalMessages > 0 ? C.value : C.dim],
252
- ]),
253
- buildPanelLine(width, [
254
- [' runner contracts ', C.label],
255
- [String(contracts.length), C.info],
256
- [' pools ', C.label],
257
- [String(pools.length), pools.length > 0 ? C.info : C.dim],
258
- [' review artifacts ', C.label],
259
- [String(artifactCount), artifactCount > 0 ? C.good : C.dim],
260
- ]),
261
- buildPanelLine(width, [
262
- [' supervisor ', C.label],
263
- [String(supervisor.sessions.length), C.info],
264
- [' degraded ', C.label],
265
- [String(supervisor.degradedConnections), supervisor.degradedConnections > 0 ? C.warn : C.good],
266
- [' distributed peers ', C.label],
267
- [String(distributed.peers?.total ?? 0), C.info],
268
- [' connected ', C.label],
269
- [String(distributed.peers?.connected ?? 0), (distributed.peers?.connected ?? 0) > 0 ? C.good : C.dim],
270
- [' queued work ', C.label],
271
- [String(distributed.work?.queued ?? 0), (distributed.work?.queued ?? 0) > 0 ? C.info : C.dim],
272
- ]),
273
- ];
274
-
275
- if (daemon.lastError) {
276
- postureLines.push(buildPanelLine(width, [
277
- [' daemon error ', C.label],
278
- [truncateDisplay(daemon.lastError, Math.max(0, width - 14)), C.bad],
279
- ]));
280
- }
281
- const canBrowse = activeConnections.length > 0 || contracts.length > 0;
282
- const canSwitch = contracts.length > 0 && activeConnections.length > 0;
283
- const navHint = !canBrowse
284
- ? ` focus=${this.browseMode} idle - no connections or contracts to browse`
285
- : canSwitch
286
- ? ` focus=${this.browseMode} Up/Down move Tab switch connections/contracts r=recover Enter=recover selection`
287
- : ` focus=${this.browseMode} Up/Down move r=recover Enter=recover selection`;
288
- const footerLines = [
289
- buildPanelLine(width, [[navHint, C.dim]]),
290
- ] as const;
291
-
292
- if (activeConnections.length === 0 && contracts.length === 0) {
293
- const idleLines = [
294
- ...postureLines,
295
- ...buildEmptyState(
296
- width,
297
- ' No active ACP or remote subagent connections.',
298
- 'The remote control room is healthy but idle. Runner contracts, session bundles, and bridge pools will appear here once remote work starts.',
299
- [
300
- { command: '/remote setup', summary: 'review remote bootstrap and environment export' },
301
- { command: '/remote env', summary: 'emit a reusable remote shell snippet' },
302
- { command: '/bridge status', summary: 'inspect runner pools and existing remote artifacts' },
303
- ],
304
- C,
305
- ),
306
- ];
307
- const lines = buildPanelWorkspace(width, height, {
308
- title: 'Remote Control Room',
309
- intro,
310
- sections: [{ lines: buildSummaryBlock(width, 'Remote posture', idleLines, C) }],
311
- footerLines,
312
- palette: C,
313
- });
314
- while (lines.length < height) lines.push(createEmptyLine(width));
315
- return lines.slice(0, height);
316
- }
317
-
318
- const viewingConnections = this.browseMode === 'connections' && activeConnections.length > 0;
319
- this.selectedIndex = Math.min(
320
- this.selectedIndex,
321
- Math.max(0, (viewingConnections ? activeConnections.length : contracts.length) - 1),
322
- );
323
- const browseCount = viewingConnections ? activeConnections.length : contracts.length;
324
- const selected = viewingConnections ? activeConnections.at(this.selectedIndex) ?? null : null;
325
- const selectedContract = !viewingConnections ? contracts.at(this.selectedIndex) ?? null : null;
326
- const detailRows: Line[] = [];
327
-
328
- if (selected) {
329
- detailRows.push(buildPanelLine(width, [
330
- [' Agent: ', C.label],
331
- [selected.agentId, C.value],
332
- [' State: ', C.label],
333
- [selected.transportState, stateColor(selected.transportState)],
334
- [' Completing: ', C.label],
335
- [selected.completing ? 'yes' : 'no', selected.completing ? C.warn : C.dim],
336
- ]));
337
- detailRows.push(buildPanelLine(width, [
338
- [' Connected: ', C.label],
339
- [formatTimestamp(selected.connectedAt), C.dim],
340
- [' Messages: ', C.label],
341
- [String(selected.messageCount), selected.messageCount > 0 ? C.info : C.dim],
342
- [' Errors: ', C.label],
343
- [String(selected.errorCount), selected.errorCount > 0 ? C.warn : C.dim],
344
- ]));
345
- if (selected.lastError) {
346
- detailRows.push(buildPanelLine(width, [
347
- [' Last error: ', C.label],
348
- [selected.lastError.slice(0, Math.max(0, width - 13)), C.bad],
349
- ]));
350
- }
351
-
352
- const contract = contracts.find((entry) => entry.runnerId === selected.agentId);
353
- if (contract) {
354
- detailRows.push(buildPanelLine(width, [
355
- [' Contract: ', C.label],
356
- [`${contract.template} / ${contract.trustClass}`, C.info],
357
- [' Pool: ', C.label],
358
- [contract.poolId ?? '(none)', contract.poolId ? C.info : C.dim],
359
- ]));
360
- detailRows.push(buildPanelLine(width, [
361
- [' Depth: ', C.label],
362
- [String(contract.capabilityCeiling.orchestrationDepth), C.value],
363
- [' Pools: ', C.label],
364
- [String(pools.length), pools.length > 0 ? C.info : C.dim],
365
- ]));
366
- detailRows.push(buildPanelLine(width, [
367
- [' Protocol: ', C.label],
368
- [contract.capabilityCeiling.executionProtocol, C.value],
369
- [' Review: ', C.label],
370
- [contract.capabilityCeiling.reviewMode, C.value],
371
- [' Lane: ', C.label],
372
- [contract.capabilityCeiling.communicationLane, C.value],
373
- ]));
374
- detailRows.push(buildPanelLine(width, [
375
- [' Tools: ', C.label],
376
- [truncate(contract.capabilityCeiling.allowedTools.join(', ') || '(none)', Math.max(0, width - 10)), C.dim],
377
- ]));
378
- }
379
-
380
- if (selected.taskId) {
381
- detailRows.push(buildPanelLine(width, [
382
- [' Task: ', C.label],
383
- [selected.taskId, C.value],
384
- ]));
385
- }
386
-
387
- const supervisorEntry = supervisor.sessions.find((entry) => entry.runnerId === selected.agentId);
388
- if (supervisorEntry) {
389
- detailRows.push(buildPanelLine(width, [
390
- [' Heartbeat: ', C.label],
391
- [supervisorEntry.heartbeat.status, supervisorEntry.heartbeat.status === 'fresh' ? C.good : supervisorEntry.heartbeat.status === 'stale' ? C.warn : C.bad],
392
- [' Protocol: ', C.label],
393
- [supervisorEntry.negotiation.executionProtocol, C.value],
394
- [' Review: ', C.label],
395
- [supervisorEntry.negotiation.reviewMode, supervisorEntry.negotiation.reviewMode === 'wrfc' ? C.good : C.dim],
396
- ]));
397
- detailRows.push(buildPanelLine(width, [[truncateDisplay(` ${supervisorEntry.heartbeat.detail}`, width), C.dim]]));
398
- if (isSupervisorEntryDegraded(supervisorEntry) && supervisorEntry.recovery.length > 0) {
399
- detailRows.push(buildPanelLine(width, [
400
- [' Enter recovers: ', C.label],
401
- [supervisorEntry.recovery[0].reason, C.warn],
402
- ]));
403
- }
404
- }
405
-
406
- const recentArtifact = snapshot.artifacts.find((artifact) => artifact.runnerId === selected.agentId);
407
- if (recentArtifact) {
408
- detailRows.push(buildPanelLine(width, [[' Recent Review Artifact', C.label]]));
409
- detailRows.push(buildPanelLine(width, [
410
- [' Artifact: ', C.label],
411
- [recentArtifact.id, C.value],
412
- [' Status: ', C.label],
413
- [recentArtifact.task.status, stateColor(recentArtifact.evidence.transportState)],
414
- ]));
415
- detailRows.push(buildPanelLine(width, [
416
- [' Summary: ', C.label],
417
- [truncate(recentArtifact.task.summary, Math.max(0, width - 12)), C.dim],
418
- ]));
419
- }
420
- detailRows.push(buildPanelLine(width, [
421
- [' Tip: ', C.label],
422
- ['Use Up/Down or j/k to inspect another connection.', C.dim],
423
- ]));
424
- } else if (selectedContract) {
425
- detailRows.push(buildPanelLine(width, [
426
- [' Runner: ', C.label],
427
- [selectedContract.runnerId, C.value],
428
- [' Template: ', C.label],
429
- [selectedContract.template, C.info],
430
- [' Trust: ', C.label],
431
- [selectedContract.trustClass, C.value],
432
- ]));
433
- detailRows.push(buildPanelLine(width, [
434
- [' Pool: ', C.label],
435
- [selectedContract.poolId ?? '(none)', selectedContract.poolId ? C.info : C.dim],
436
- [' Transport: ', C.label],
437
- [selectedContract.transport.state, stateColor(selectedContract.transport.state)],
438
- ]));
439
- detailRows.push(buildPanelLine(width, [
440
- [' Protocol: ', C.label],
441
- [selectedContract.capabilityCeiling.executionProtocol, C.value],
442
- [' Review: ', C.label],
443
- [selectedContract.capabilityCeiling.reviewMode, C.value],
444
- [' Lane: ', C.label],
445
- [selectedContract.capabilityCeiling.communicationLane, C.value],
446
- ]));
447
- detailRows.push(buildPanelLine(width, [
448
- [' Tools: ', C.label],
449
- [truncate(selectedContract.capabilityCeiling.allowedTools.join(', ') || '(none)', Math.max(0, width - 10)), C.dim],
450
- ]));
451
- const supervisorEntry = supervisor.sessions.find((entry) => entry.runnerId === selectedContract.runnerId);
452
- if (supervisorEntry) {
453
- detailRows.push(buildPanelLine(width, [
454
- [' Heartbeat: ', C.label],
455
- [supervisorEntry.heartbeat.status, supervisorEntry.heartbeat.status === 'fresh' ? C.good : supervisorEntry.heartbeat.status === 'stale' ? C.warn : C.bad],
456
- [' Lane: ', C.label],
457
- [supervisorEntry.negotiation.communicationLane, C.info],
458
- ]));
459
- if (isSupervisorEntryDegraded(supervisorEntry) && supervisorEntry.recovery.length > 0) {
460
- detailRows.push(buildPanelLine(width, [
461
- [' Enter recovers: ', C.label],
462
- [supervisorEntry.recovery[0].reason, C.warn],
463
- ]));
464
- }
465
- }
466
- const recentArtifact = snapshot.artifacts.find((artifact) => artifact.runnerId === selectedContract.runnerId);
467
- if (recentArtifact) {
468
- detailRows.push(buildPanelLine(width, [
469
- [' Recent artifact: ', C.label],
470
- [recentArtifact.id, C.good],
471
- [' Status: ', C.label],
472
- [recentArtifact.task.status, stateColor(recentArtifact.evidence.transportState)],
473
- ]));
474
- }
475
- }
476
- const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Remote posture', postureLines, C) };
477
- const detailSection: PanelWorkspaceSection = {
478
- lines: buildDetailBlock(width, selected ? 'Selected connection' : 'Selected contract', detailRows, C),
479
- };
480
- const browseTitle = viewingConnections ? 'Active Connections' : 'Registered Remote Runner Contracts';
481
- const rawBrowseLines: Line[] = viewingConnections
482
- ? activeConnections.map((connection, absolute) => {
483
- return buildPanelListRow(width, [
484
- { text: connection.agentId.padEnd(18), fg: C.value },
485
- { text: ` ${connection.transportState.padEnd(18)}`, fg: stateColor(connection.transportState) },
486
- { text: ` msgs=${String(connection.messageCount).padEnd(6)}`, fg: C.info },
487
- { text: ` errs=${String(connection.errorCount).padEnd(4)}`, fg: connection.errorCount > 0 ? C.warn : C.dim },
488
- { text: truncateDisplay(` ${connection.label}`, Math.max(0, width - 56)), fg: C.dim },
489
- ], C, { selected: absolute === this.selectedIndex, selectedBg: C.headerBg });
490
- })
491
- : [
492
- ...contracts.map((contract, absolute) => {
493
- return buildPanelListRow(width, [
494
- { text: contract.runnerId.padEnd(18), fg: C.value },
495
- { text: ` ${contract.transport.state.padEnd(18)}`, fg: stateColor(contract.transport.state) },
496
- { text: truncateDisplay(` ${contract.template}`, Math.max(0, width - 42)), fg: C.dim },
497
- ], C, { selected: absolute === this.selectedIndex, selectedBg: C.headerBg });
498
- }),
499
- buildPanelLine(width, [[' No active connection is currently attached to these contracts.', C.dim]]),
500
- ];
501
- const resolvedBrowseSection = resolvePrimaryScrollableSection(width, height, {
502
- intro,
503
- footerLines,
504
- palette: C,
505
- beforeSections: [postureSection],
506
- section: {
507
- title: browseTitle,
508
- scrollableLines: rawBrowseLines,
509
- selectedIndex: this.selectedIndex,
510
- scrollOffset: this.scrollOffset,
511
- guardRows: 1,
512
- minRows: 4,
513
- appendWindowSummary: viewingConnections ? { dimColor: C.dim } : undefined,
514
- },
515
- afterSections: [detailSection],
516
- });
517
- this.scrollOffset = resolvedBrowseSection.scrollOffset;
518
-
519
- const sections: PanelWorkspaceSection[] = [
520
- postureSection,
521
- resolvedBrowseSection.section,
522
- detailSection,
523
- ];
524
- const lines = buildPanelWorkspace(width, height, {
525
- title: 'Remote Control Room',
526
- intro,
527
- sections,
528
- footerLines,
529
- palette: C,
530
- });
531
- while (lines.length < height) lines.push(createEmptyLine(width));
532
- return lines.slice(0, height);
533
- }
534
- }