@jmoyers/harness 0.1.11 → 0.1.20

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 (232) hide show
  1. package/README.md +31 -39
  2. package/package.json +31 -11
  3. package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
  4. package/packages/harness-ai/src/stream-text.ts +13 -91
  5. package/packages/harness-ui/src/frame-primitives.ts +158 -0
  6. package/packages/harness-ui/src/index.ts +18 -0
  7. package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
  8. package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
  9. package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
  10. package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
  11. package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
  12. package/packages/harness-ui/src/interaction/input.ts +420 -0
  13. package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
  14. package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
  15. package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
  16. package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
  17. package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
  18. package/packages/harness-ui/src/kit.ts +476 -0
  19. package/packages/harness-ui/src/layout.ts +238 -0
  20. package/packages/harness-ui/src/modal-manager.ts +222 -0
  21. package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
  22. package/packages/harness-ui/src/surface.ts +252 -0
  23. package/packages/harness-ui/src/text-layout.ts +210 -0
  24. package/packages/nim-core/src/contracts.ts +239 -0
  25. package/packages/nim-core/src/event-store.ts +299 -0
  26. package/packages/nim-core/src/events.ts +53 -0
  27. package/packages/nim-core/src/index.ts +9 -0
  28. package/packages/nim-core/src/provider-router.ts +129 -0
  29. package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
  30. package/packages/nim-core/src/runtime-factory.ts +49 -0
  31. package/packages/nim-core/src/runtime.ts +1797 -0
  32. package/packages/nim-core/src/session-store.ts +516 -0
  33. package/packages/nim-core/src/telemetry.ts +48 -0
  34. package/packages/nim-test-tui/src/index.ts +150 -0
  35. package/packages/nim-ui-core/src/index.ts +1 -0
  36. package/packages/nim-ui-core/src/projection.ts +87 -0
  37. package/scripts/codex-live-mux-runtime.ts +2 -3872
  38. package/scripts/control-plane-daemon.ts +11 -0
  39. package/scripts/harness-bin.js +5 -0
  40. package/scripts/harness-commands.ts +300 -0
  41. package/scripts/harness-runtime.ts +82 -0
  42. package/scripts/harness.ts +33 -3019
  43. package/scripts/nim-tui-smoke.ts +748 -0
  44. package/src/cli/auth/runtime.ts +948 -0
  45. package/src/cli/gateway/runtime.ts +1872 -0
  46. package/src/cli/parsing/flags.ts +23 -0
  47. package/src/cli/parsing/session.ts +42 -0
  48. package/src/cli/runtime/context.ts +193 -0
  49. package/src/cli/runtime-app/application.ts +392 -0
  50. package/src/cli/runtime-infra/gateway-control.ts +729 -0
  51. package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
  52. package/src/cli/workflows/runtime.ts +965 -0
  53. package/src/clients/tui/left-rail-interactions.ts +519 -0
  54. package/src/clients/tui/main-pane-interactions.ts +509 -0
  55. package/src/clients/tui/modal-input-routing.ts +71 -0
  56. package/src/clients/tui/render-snapshot-adapter.ts +88 -0
  57. package/src/clients/web/synced-selectors.ts +132 -0
  58. package/src/codex/live-session.ts +82 -29
  59. package/src/config/config-core.ts +348 -8
  60. package/src/config/harness.config.template.jsonc +33 -0
  61. package/src/control-plane/agent-realtime-api.ts +82 -427
  62. package/src/control-plane/session-summary.ts +10 -81
  63. package/src/control-plane/status/reducer-base.ts +12 -12
  64. package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
  65. package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
  66. package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
  67. package/src/control-plane/stream-client.ts +12 -2
  68. package/src/control-plane/stream-command-parser.ts +83 -143
  69. package/src/control-plane/stream-protocol.ts +53 -37
  70. package/src/control-plane/stream-server-command.ts +376 -69
  71. package/src/control-plane/stream-server-session-runtime.ts +3 -2
  72. package/src/control-plane/stream-server.ts +864 -70
  73. package/src/control-plane/stream-session-runtime-types.ts +41 -0
  74. package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
  75. package/src/core/state/observed-stream-cursor.ts +43 -0
  76. package/src/core/state/synced-observed-state.ts +273 -0
  77. package/src/core/store/harness-synced-store.ts +81 -0
  78. package/src/diff/budget.ts +136 -0
  79. package/src/diff/build.ts +289 -0
  80. package/src/diff/chunker.ts +146 -0
  81. package/src/diff/git-invoke.ts +315 -0
  82. package/src/diff/git-parse.ts +472 -0
  83. package/src/diff/hash.ts +70 -0
  84. package/src/diff/index.ts +24 -0
  85. package/src/diff/normalize.ts +134 -0
  86. package/src/diff/types.ts +178 -0
  87. package/src/diff-ui/args.ts +346 -0
  88. package/src/diff-ui/commands.ts +123 -0
  89. package/src/diff-ui/finder.ts +94 -0
  90. package/src/diff-ui/highlight.ts +127 -0
  91. package/src/diff-ui/index.ts +2 -0
  92. package/src/diff-ui/model.ts +141 -0
  93. package/src/diff-ui/pager.ts +412 -0
  94. package/src/diff-ui/render.ts +337 -0
  95. package/src/diff-ui/runtime.ts +379 -0
  96. package/src/diff-ui/state.ts +224 -0
  97. package/src/diff-ui/types.ts +236 -0
  98. package/src/domain/workspace.ts +68 -5
  99. package/src/mux/control-plane-op-queue.ts +93 -7
  100. package/src/mux/conversation-rail.ts +28 -71
  101. package/src/mux/dual-pane-core.ts +13 -13
  102. package/src/mux/harness-core-ui.ts +313 -42
  103. package/src/mux/input-shortcuts.ts +13 -131
  104. package/src/mux/keybinding-catalog.ts +340 -0
  105. package/src/mux/keybinding-registry.ts +103 -0
  106. package/src/mux/live-mux/command-menu-open-in.ts +280 -0
  107. package/src/mux/live-mux/command-menu.ts +167 -4
  108. package/src/mux/live-mux/conversation-state.ts +13 -0
  109. package/src/mux/live-mux/directory-resolution.ts +1 -1
  110. package/src/mux/live-mux/git-snapshot.ts +33 -2
  111. package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
  112. package/src/mux/live-mux/home-pane-drop.ts +1 -1
  113. package/src/mux/live-mux/home-pane-pointer.ts +10 -0
  114. package/src/mux/live-mux/input-forwarding.ts +59 -2
  115. package/src/mux/live-mux/left-nav-activation.ts +124 -7
  116. package/src/mux/live-mux/left-nav.ts +35 -0
  117. package/src/mux/live-mux/link-click.ts +292 -0
  118. package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
  119. package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
  120. package/src/mux/live-mux/modal-input-reducers.ts +77 -12
  121. package/src/mux/live-mux/modal-overlays.ts +168 -34
  122. package/src/mux/live-mux/modal-pointer.ts +3 -7
  123. package/src/mux/live-mux/modal-prompt-handlers.ts +23 -2
  124. package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
  125. package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
  126. package/src/mux/live-mux/pointer-routing.ts +5 -2
  127. package/src/mux/live-mux/project-pane-pointer.ts +8 -0
  128. package/src/mux/live-mux/rail-layout.ts +33 -30
  129. package/src/mux/live-mux/release-notes.ts +383 -0
  130. package/src/mux/live-mux/render-trace-analysis.ts +52 -7
  131. package/src/mux/live-mux/repository-folding.ts +3 -0
  132. package/src/mux/live-mux/selection.ts +0 -4
  133. package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
  134. package/src/mux/project-pane-github-review.ts +271 -0
  135. package/src/mux/render-frame.ts +4 -0
  136. package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
  137. package/src/mux/task-composer.ts +21 -14
  138. package/src/mux/task-focused-pane.ts +118 -117
  139. package/src/mux/task-screen-keybindings.ts +10 -101
  140. package/src/mux/workspace-rail-model.ts +270 -104
  141. package/src/mux/workspace-rail.ts +45 -22
  142. package/src/pty/session-broker.ts +1 -1
  143. package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
  144. package/src/services/control-plane.ts +50 -32
  145. package/src/services/conversation-lifecycle.ts +118 -87
  146. package/src/services/conversation-startup-hydration.ts +20 -12
  147. package/src/services/directory-hydration.ts +21 -16
  148. package/src/services/event-persistence.ts +7 -0
  149. package/src/services/left-rail-pointer-handler.ts +329 -0
  150. package/src/services/mux-ui-state-persistence.ts +5 -1
  151. package/src/services/recording.ts +34 -26
  152. package/src/services/runtime-command-menu-agent-tools.ts +1 -1
  153. package/src/services/runtime-control-actions.ts +79 -61
  154. package/src/services/runtime-control-plane-ops.ts +122 -83
  155. package/src/services/runtime-conversation-actions.ts +40 -26
  156. package/src/services/runtime-conversation-activation.ts +73 -46
  157. package/src/services/runtime-conversation-starter.ts +53 -45
  158. package/src/services/runtime-conversation-title-edit.ts +91 -80
  159. package/src/services/runtime-envelope-handler.ts +107 -105
  160. package/src/services/runtime-git-state.ts +42 -29
  161. package/src/services/runtime-layout-resize.ts +3 -1
  162. package/src/services/runtime-left-rail-render.ts +99 -63
  163. package/src/services/runtime-nim-cli-session.ts +438 -0
  164. package/src/services/runtime-nim-session.ts +705 -0
  165. package/src/services/runtime-nim-tool-bridge.ts +141 -0
  166. package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
  167. package/src/services/runtime-process-wiring.ts +29 -36
  168. package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
  169. package/src/services/runtime-render-flush.ts +63 -70
  170. package/src/services/runtime-render-lifecycle.ts +65 -64
  171. package/src/services/runtime-render-orchestrator.ts +55 -45
  172. package/src/services/runtime-render-pipeline.ts +106 -103
  173. package/src/services/runtime-render-state.ts +62 -49
  174. package/src/services/runtime-repository-actions.ts +97 -72
  175. package/src/services/runtime-right-pane-render.ts +80 -53
  176. package/src/services/runtime-shutdown.ts +38 -35
  177. package/src/services/runtime-stream-subscriptions.ts +35 -27
  178. package/src/services/runtime-task-composer-persistence.ts +71 -59
  179. package/src/services/runtime-task-composer-snapshot.ts +14 -0
  180. package/src/services/runtime-task-editor-actions.ts +46 -29
  181. package/src/services/runtime-task-pane-actions.ts +220 -134
  182. package/src/services/runtime-task-pane-shortcuts.ts +323 -123
  183. package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
  184. package/src/services/runtime-workspace-observed-events.ts +33 -184
  185. package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
  186. package/src/services/session-diagnostics-store.ts +217 -0
  187. package/src/services/startup-background-resume.ts +26 -21
  188. package/src/services/startup-orchestrator.ts +16 -13
  189. package/src/services/startup-paint-tracker.ts +29 -21
  190. package/src/services/startup-persisted-conversation-queue.ts +19 -13
  191. package/src/services/startup-settled-gate.ts +25 -15
  192. package/src/services/startup-shutdown.ts +18 -22
  193. package/src/services/startup-state-hydration.ts +44 -34
  194. package/src/services/startup-visibility.ts +12 -4
  195. package/src/services/task-pane-selection-actions.ts +89 -72
  196. package/src/services/task-planning-hydration.ts +24 -18
  197. package/src/services/task-planning-observed-events.ts +50 -52
  198. package/src/services/workspace-observed-events.ts +66 -63
  199. package/src/storage/storage-lifecycle-core.ts +438 -0
  200. package/src/store/control-plane-store-normalize.ts +33 -242
  201. package/src/store/control-plane-store-types.ts +1 -35
  202. package/src/store/control-plane-store.ts +360 -56
  203. package/src/store/event-store.ts +366 -8
  204. package/src/terminal/snapshot-oracle.ts +207 -94
  205. package/src/ui/mux-theme.ts +112 -8
  206. package/src/ui/panes/home-gridfire.ts +40 -31
  207. package/src/ui/panes/home.ts +10 -2
  208. package/src/ui/panes/nim.ts +315 -0
  209. package/src/mux/live-mux/actions-task.ts +0 -115
  210. package/src/mux/live-mux/left-rail-actions.ts +0 -118
  211. package/src/mux/live-mux/left-rail-conversation-click.ts +0 -85
  212. package/src/mux/live-mux/left-rail-pointer.ts +0 -74
  213. package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
  214. package/src/services/runtime-directory-actions.ts +0 -164
  215. package/src/services/runtime-input-pipeline.ts +0 -50
  216. package/src/services/runtime-input-router.ts +0 -195
  217. package/src/services/runtime-main-pane-input.ts +0 -230
  218. package/src/services/runtime-modal-input.ts +0 -137
  219. package/src/services/runtime-navigation-input.ts +0 -197
  220. package/src/services/runtime-rail-input.ts +0 -279
  221. package/src/services/runtime-task-pane.ts +0 -62
  222. package/src/services/runtime-workspace-actions.ts +0 -158
  223. package/src/ui/conversation-input-forwarder.ts +0 -114
  224. package/src/ui/conversation-selection-input.ts +0 -103
  225. package/src/ui/global-shortcut-input.ts +0 -89
  226. package/src/ui/input.ts +0 -269
  227. package/src/ui/kit.ts +0 -509
  228. package/src/ui/left-nav-input.ts +0 -80
  229. package/src/ui/left-rail-pointer-input.ts +0 -148
  230. package/src/ui/modals/manager.ts +0 -218
  231. package/src/ui/repository-fold-input.ts +0 -91
  232. package/src/ui/surface.ts +0 -224
@@ -1,11 +1,27 @@
1
1
  export type ControlPlaneOpPriority = 'interactive' | 'background';
2
2
 
3
+ type ControlPlaneOpSupersedeMode = 'none' | 'pending' | 'pending-and-running';
4
+
5
+ interface ControlPlaneOpTaskOptions {
6
+ readonly signal: AbortSignal;
7
+ }
8
+
9
+ type ControlPlaneOpTask = (options: ControlPlaneOpTaskOptions) => Promise<void>;
10
+
11
+ interface ControlPlaneOpEnqueueOptions {
12
+ readonly key?: string;
13
+ readonly supersede?: ControlPlaneOpSupersedeMode;
14
+ }
15
+
3
16
  interface QueuedControlPlaneOp {
4
17
  readonly id: number;
5
18
  readonly priority: ControlPlaneOpPriority;
6
19
  readonly label: string;
7
20
  readonly enqueuedAtMs: number;
8
- readonly task: () => Promise<void>;
21
+ readonly key: string | null;
22
+ readonly supersede: ControlPlaneOpSupersedeMode;
23
+ readonly controller: AbortController;
24
+ readonly task: ControlPlaneOpTask;
9
25
  }
10
26
 
11
27
  interface ControlPlaneOpQueueMetrics {
@@ -45,6 +61,11 @@ interface ControlPlaneOpQueueOptions {
45
61
  metrics: ControlPlaneOpQueueMetrics,
46
62
  error: unknown,
47
63
  ) => void;
64
+ readonly onCanceled?: (
65
+ event: ControlPlaneOpQueueStartEvent,
66
+ metrics: ControlPlaneOpQueueMetrics,
67
+ reason: 'pre-start-abort' | 'running-abort',
68
+ ) => void;
48
69
  readonly onFatal?: (error: unknown) => void;
49
70
  }
50
71
 
@@ -59,6 +80,7 @@ export class ControlPlaneOpQueue {
59
80
  private readonly onStart: ControlPlaneOpQueueOptions['onStart'];
60
81
  private readonly onSuccess: ControlPlaneOpQueueOptions['onSuccess'];
61
82
  private readonly onError: ControlPlaneOpQueueOptions['onError'];
83
+ private readonly onCanceled: ControlPlaneOpQueueOptions['onCanceled'];
62
84
  private readonly onFatal: ControlPlaneOpQueueOptions['onFatal'];
63
85
 
64
86
  private readonly interactiveQueue: QueuedControlPlaneOp[] = [];
@@ -67,6 +89,7 @@ export class ControlPlaneOpQueue {
67
89
  private nextId = 1;
68
90
  private running = false;
69
91
  private pumpScheduled = false;
92
+ private runningOp: QueuedControlPlaneOp | null = null;
70
93
 
71
94
  constructor(options: ControlPlaneOpQueueOptions = {}) {
72
95
  this.nowMs = options.nowMs ?? Date.now;
@@ -75,15 +98,24 @@ export class ControlPlaneOpQueue {
75
98
  this.onStart = options.onStart;
76
99
  this.onSuccess = options.onSuccess;
77
100
  this.onError = options.onError;
101
+ this.onCanceled = options.onCanceled;
78
102
  this.onFatal = options.onFatal;
79
103
  }
80
104
 
81
- enqueueInteractive(task: () => Promise<void>, label = 'interactive-op'): void {
82
- this.enqueue(task, 'interactive', label);
105
+ enqueueInteractive(
106
+ task: ControlPlaneOpTask,
107
+ label = 'interactive-op',
108
+ options: ControlPlaneOpEnqueueOptions = {},
109
+ ): void {
110
+ this.enqueue(task, 'interactive', label, options);
83
111
  }
84
112
 
85
- enqueueBackground(task: () => Promise<void>, label = 'background-op'): void {
86
- this.enqueue(task, 'background', label);
113
+ enqueueBackground(
114
+ task: ControlPlaneOpTask,
115
+ label = 'background-op',
116
+ options: ControlPlaneOpEnqueueOptions = {},
117
+ ): void {
118
+ this.enqueue(task, 'background', label, options);
87
119
  }
88
120
 
89
121
  async waitForDrain(): Promise<void> {
@@ -104,15 +136,33 @@ export class ControlPlaneOpQueue {
104
136
  }
105
137
 
106
138
  private enqueue(
107
- task: () => Promise<void>,
139
+ task: ControlPlaneOpTask,
108
140
  priority: ControlPlaneOpPriority,
109
141
  label: string,
142
+ options: ControlPlaneOpEnqueueOptions,
110
143
  ): void {
144
+ const key = options.key?.trim() ?? null;
145
+ const supersede = options.supersede ?? 'none';
146
+ if (key !== null && (supersede === 'pending' || supersede === 'pending-and-running')) {
147
+ this.abortPendingByKey(key);
148
+ }
149
+ if (
150
+ key !== null &&
151
+ supersede === 'pending-and-running' &&
152
+ this.runningOp !== null &&
153
+ this.runningOp.key === key
154
+ ) {
155
+ this.runningOp.controller.abort();
156
+ }
157
+
111
158
  const op: QueuedControlPlaneOp = {
112
159
  id: this.nextId,
113
160
  priority,
114
161
  label,
115
162
  enqueuedAtMs: this.nowMs(),
163
+ key,
164
+ supersede,
165
+ controller: new AbortController(),
116
166
  task,
117
167
  };
118
168
  this.nextId += 1;
@@ -135,6 +185,26 @@ export class ControlPlaneOpQueue {
135
185
  this.schedulePump();
136
186
  }
137
187
 
188
+ private abortPendingByKey(key: string): void {
189
+ const filterPending = (queue: QueuedControlPlaneOp[]): QueuedControlPlaneOp[] => {
190
+ const kept: QueuedControlPlaneOp[] = [];
191
+ for (const entry of queue) {
192
+ if (entry.key === key) {
193
+ entry.controller.abort();
194
+ continue;
195
+ }
196
+ kept.push(entry);
197
+ }
198
+ return kept;
199
+ };
200
+ const nextInteractive = filterPending(this.interactiveQueue);
201
+ const nextBackground = filterPending(this.backgroundQueue);
202
+ this.interactiveQueue.length = 0;
203
+ this.backgroundQueue.length = 0;
204
+ this.interactiveQueue.push(...nextInteractive);
205
+ this.backgroundQueue.push(...nextBackground);
206
+ }
207
+
138
208
  private metricsSnapshot(): ControlPlaneOpQueueMetrics {
139
209
  return {
140
210
  interactiveQueued: this.interactiveQueue.length,
@@ -197,13 +267,29 @@ export class ControlPlaneOpQueue {
197
267
 
198
268
  try {
199
269
  this.onStart?.(startEvent, this.metricsSnapshot());
270
+ if (next.controller.signal.aborted) {
271
+ this.onCanceled?.(startEvent, this.metricsSnapshot(), 'pre-start-abort');
272
+ return;
273
+ }
274
+ this.runningOp = next;
200
275
  try {
201
- await next.task();
276
+ await next.task({
277
+ signal: next.controller.signal,
278
+ });
279
+ if (next.controller.signal.aborted) {
280
+ this.onCanceled?.(startEvent, this.metricsSnapshot(), 'running-abort');
281
+ return;
282
+ }
202
283
  this.onSuccess?.(startEvent, this.metricsSnapshot());
203
284
  } catch (error: unknown) {
285
+ if (next.controller.signal.aborted) {
286
+ this.onCanceled?.(startEvent, this.metricsSnapshot(), 'running-abort');
287
+ return;
288
+ }
204
289
  this.onError?.(startEvent, this.metricsSnapshot(), error);
205
290
  }
206
291
  } finally {
292
+ this.runningOp = null;
207
293
  this.running = false;
208
294
  this.resolveDrainIfIdle();
209
295
  this.schedulePump();
@@ -4,14 +4,11 @@ import type {
4
4
  StreamSessionStatusModel,
5
5
  } from '../control-plane/stream-protocol.ts';
6
6
  import { padOrTrimDisplay } from './dual-pane-core.ts';
7
- import {
8
- createUiSurface,
9
- DEFAULT_UI_STYLE,
10
- drawUiText,
11
- fillUiRow,
12
- renderUiSurfaceAnsiRows,
13
- } from '../ui/surface.ts';
14
- import { paintUiRow } from '../ui/kit.ts';
7
+ import { UiKit } from '../../packages/harness-ui/src/kit.ts';
8
+ import { DEFAULT_UI_STYLE, SurfaceBuffer } from '../../packages/harness-ui/src/surface.ts';
9
+ import { getActiveMuxTheme } from '../ui/mux-theme.ts';
10
+
11
+ const UI_KIT = new UiKit();
15
12
 
16
13
  export interface ConversationRailSessionSummary {
17
14
  readonly sessionId: string;
@@ -194,56 +191,6 @@ export function buildConversationRailLines(
194
191
  return rows.map((row) => padOrTrimDisplay(row.text, safeWidth));
195
192
  }
196
193
 
197
- const HEADER_ROW_STYLE = {
198
- fg: { kind: 'indexed', index: 250 } as const,
199
- bg: { kind: 'indexed', index: 236 } as const,
200
- bold: false,
201
- };
202
- const NORMAL_ROW_STYLE = DEFAULT_UI_STYLE;
203
- const ACTIVE_ROW_STYLE = {
204
- fg: { kind: 'indexed', index: 255 } as const,
205
- bg: { kind: 'indexed', index: 238 } as const,
206
- bold: false,
207
- };
208
- const ACTIVE_INDICATOR_STYLE = {
209
- fg: { kind: 'indexed', index: 231 } as const,
210
- bg: { kind: 'indexed', index: 238 } as const,
211
- bold: true,
212
- };
213
- const BADGE_STYLES = {
214
- 'needs-input': {
215
- fg: { kind: 'indexed', index: 231 } as const,
216
- bg: { kind: 'indexed', index: 166 } as const,
217
- bold: true,
218
- },
219
- running: {
220
- fg: { kind: 'indexed', index: 231 } as const,
221
- bg: { kind: 'indexed', index: 24 } as const,
222
- bold: true,
223
- },
224
- completed: {
225
- fg: { kind: 'indexed', index: 231 } as const,
226
- bg: { kind: 'indexed', index: 28 } as const,
227
- bold: true,
228
- },
229
- exited: {
230
- fg: { kind: 'indexed', index: 250 } as const,
231
- bg: { kind: 'indexed', index: 239 } as const,
232
- bold: true,
233
- },
234
- } as const;
235
- const ACTIVE_TEXT_STYLE = {
236
- fg: { kind: 'indexed', index: 255 } as const,
237
- bg: { kind: 'indexed', index: 238 } as const,
238
- bold: false,
239
- };
240
- const NORMAL_TEXT_STYLE = DEFAULT_UI_STYLE;
241
- const MUTED_TEXT_STYLE = {
242
- fg: { kind: 'indexed', index: 245 } as const,
243
- bg: { kind: 'default' } as const,
244
- bold: false,
245
- };
246
-
247
194
  function badgeLabel(status: StreamSessionRuntimeStatus): string {
248
195
  if (status === 'needs-input') {
249
196
  return 'NEED';
@@ -276,45 +223,55 @@ export function renderConversationRailAnsiRows(
276
223
  ): readonly string[] {
277
224
  const safeWidth = Math.max(1, width);
278
225
  const rows = buildConversationRailRows(sessions, activeSessionId, maxRows, order);
279
- const surface = createUiSurface(safeWidth, rows.length, DEFAULT_UI_STYLE);
226
+ const surface = new SurfaceBuffer(safeWidth, rows.length, DEFAULT_UI_STYLE);
227
+ const theme = getActiveMuxTheme().conversationRail;
280
228
 
281
229
  for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
282
230
  const row = rows[rowIndex]!;
283
231
  if (row.kind === 'header') {
284
- paintUiRow(surface, rowIndex, row.text, HEADER_ROW_STYLE, HEADER_ROW_STYLE, 1);
232
+ UI_KIT.paintRow(surface, rowIndex, row.text, theme.headerStyle, theme.headerStyle, 1);
285
233
  continue;
286
234
  }
287
235
 
288
236
  if (row.kind === 'empty') {
289
- paintUiRow(surface, rowIndex, '', NORMAL_ROW_STYLE, NORMAL_ROW_STYLE);
237
+ UI_KIT.paintRow(surface, rowIndex, '', theme.normalRowStyle, theme.normalRowStyle);
290
238
  continue;
291
239
  }
292
240
 
293
241
  const session = row.session!;
294
242
  const active = row.active === true;
295
- fillUiRow(surface, rowIndex, active ? ACTIVE_ROW_STYLE : NORMAL_ROW_STYLE);
296
- drawUiText(
297
- surface,
243
+ surface.fillRow(rowIndex, active ? theme.activeRowStyle : theme.normalRowStyle);
244
+ surface.drawText(
298
245
  0,
299
246
  rowIndex,
300
247
  active ? '>' : ' ',
301
- active ? ACTIVE_INDICATOR_STYLE : NORMAL_TEXT_STYLE,
248
+ active ? theme.activeIndicatorStyle : theme.normalTextStyle,
249
+ );
250
+ surface.drawText(
251
+ 2,
252
+ rowIndex,
253
+ badgeLabel(session.status),
254
+ session.status === 'needs-input'
255
+ ? theme.statusBadgeStyles.needsInput
256
+ : session.status === 'running'
257
+ ? theme.statusBadgeStyles.running
258
+ : session.status === 'completed'
259
+ ? theme.statusBadgeStyles.completed
260
+ : theme.statusBadgeStyles.exited,
302
261
  );
303
- drawUiText(surface, 2, rowIndex, badgeLabel(session.status), BADGE_STYLES[session.status]);
304
- drawUiText(
305
- surface,
262
+ surface.drawText(
306
263
  7,
307
264
  rowIndex,
308
265
  rowBodyText(session),
309
- active ? ACTIVE_TEXT_STYLE : NORMAL_TEXT_STYLE,
266
+ active ? theme.activeTextStyle : theme.normalTextStyle,
310
267
  );
311
268
 
312
269
  if (!session.live && session.attentionReason === null) {
313
- drawUiText(surface, 7, rowIndex, rowBodyText(session), MUTED_TEXT_STYLE);
270
+ surface.drawText(7, rowIndex, rowBodyText(session), theme.deadTextStyle);
314
271
  }
315
272
  }
316
273
 
317
- return renderUiSurfaceAnsiRows(surface);
274
+ return surface.renderAnsiRows();
318
275
  }
319
276
 
320
277
  export function cycleConversationId(
@@ -1,4 +1,5 @@
1
1
  import { measureDisplayWidth, wrapTextForColumns } from '../terminal/snapshot-oracle.ts';
2
+ import { computeDualPaneLayoutWithLayers } from '../../packages/harness-ui/src/layout.ts';
2
3
 
3
4
  const MIN_LEFT_PANE_COLS = 28;
4
5
  const MIN_RIGHT_PANE_COLS = 20;
@@ -92,22 +93,21 @@ export function computeDualPaneLayout(
92
93
  ): DualPaneLayout {
93
94
  const normalizedCols = Math.max(3, cols);
94
95
  const normalizedRows = Math.max(2, rows);
95
- const paneRows = Math.max(1, normalizedRows - 1);
96
- const statusRow = paneRows + 1;
97
-
98
- const availablePaneCols = normalizedCols - 1;
99
96
  const leftCols = resolveLeftPaneCols(normalizedCols, options.leftCols ?? null);
100
- const rightCols = availablePaneCols - leftCols;
97
+ const layout = computeDualPaneLayoutWithLayers(normalizedCols, normalizedRows, {
98
+ leftCols,
99
+ statusRows: 1,
100
+ });
101
101
 
102
102
  return {
103
- cols: normalizedCols,
104
- rows: normalizedRows,
105
- paneRows,
106
- statusRow,
107
- leftCols,
108
- rightCols,
109
- separatorCol: leftCols + 1,
110
- rightStartCol: leftCols + 2,
103
+ cols: layout.cols,
104
+ rows: layout.rows,
105
+ paneRows: layout.paneRows,
106
+ statusRow: layout.statusRow,
107
+ leftCols: layout.leftCols,
108
+ rightCols: layout.rightCols,
109
+ separatorCol: layout.separatorCol,
110
+ rightStartCol: layout.rightStartCol,
111
111
  };
112
112
  }
113
113