@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,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
- }