@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
@@ -0,0 +1,224 @@
1
+ import { buildFinderResults } from './finder.ts';
2
+ import { maxTopRowForModel } from './model.ts';
3
+ import type { DiffUiModel, DiffUiState, DiffUiStateAction, DiffUiViewMode } from './types.ts';
4
+
5
+ const AUTO_SPLIT_MIN_WIDTH = 120;
6
+
7
+ function clamp(value: number, min: number, max: number): number {
8
+ if (value < min) {
9
+ return min;
10
+ }
11
+ if (value > max) {
12
+ return max;
13
+ }
14
+ return value;
15
+ }
16
+
17
+ function clampIndex(value: number, length: number): number {
18
+ if (length <= 0) {
19
+ return 0;
20
+ }
21
+ return clamp(value, 0, length - 1);
22
+ }
23
+
24
+ function firstHunkIndexForFile(model: DiffUiModel, fileIndex: number): number {
25
+ const row = model.rows.find(
26
+ (candidate) => candidate.kind === 'hunk-header' && candidate.fileIndex === fileIndex,
27
+ );
28
+ return row?.hunkIndex ?? 0;
29
+ }
30
+
31
+ function resolveTopForFile(model: DiffUiModel, fileIndex: number): number {
32
+ if (model.fileStartRows.length === 0) {
33
+ return 0;
34
+ }
35
+ return model.fileStartRows[clampIndex(fileIndex, model.fileStartRows.length)] ?? 0;
36
+ }
37
+
38
+ function resolveTopForHunk(model: DiffUiModel, hunkIndex: number): number {
39
+ if (model.hunkStartRows.length === 0) {
40
+ return 0;
41
+ }
42
+ return model.hunkStartRows[clampIndex(hunkIndex, model.hunkStartRows.length)] ?? 0;
43
+ }
44
+
45
+ export function resolveEffectiveViewMode(
46
+ viewMode: DiffUiViewMode,
47
+ viewportWidth: number,
48
+ ): 'split' | 'unified' {
49
+ if (viewMode === 'split') {
50
+ return 'split';
51
+ }
52
+ if (viewMode === 'unified') {
53
+ return 'unified';
54
+ }
55
+ return viewportWidth >= AUTO_SPLIT_MIN_WIDTH ? 'split' : 'unified';
56
+ }
57
+
58
+ export function createInitialDiffUiState(
59
+ model: DiffUiModel,
60
+ viewMode: DiffUiViewMode,
61
+ viewportWidth: number,
62
+ ): DiffUiState {
63
+ return {
64
+ viewMode,
65
+ effectiveViewMode: resolveEffectiveViewMode(viewMode, viewportWidth),
66
+ topRow: 0,
67
+ activeFileIndex: model.diff.files.length === 0 ? 0 : 0,
68
+ activeHunkIndex: model.hunkStartRows.length === 0 ? 0 : 0,
69
+ finderOpen: false,
70
+ finderQuery: '',
71
+ finderSelectedIndex: 0,
72
+ finderResults: buildFinderResults(model, ''),
73
+ searchQuery: '',
74
+ };
75
+ }
76
+
77
+ function withClampedTop(
78
+ state: DiffUiState,
79
+ model: DiffUiModel,
80
+ viewportHeight: number,
81
+ ): DiffUiState {
82
+ const maxTop = maxTopRowForModel(model, viewportHeight);
83
+ return {
84
+ ...state,
85
+ topRow: clamp(state.topRow, 0, maxTop),
86
+ };
87
+ }
88
+
89
+ export function reduceDiffUiState(input: {
90
+ readonly model: DiffUiModel;
91
+ readonly state: DiffUiState;
92
+ readonly action: DiffUiStateAction;
93
+ readonly viewportWidth: number;
94
+ readonly viewportHeight: number;
95
+ }): DiffUiState {
96
+ const { model, action, viewportHeight, viewportWidth } = input;
97
+ let next: DiffUiState = input.state;
98
+
99
+ if (action.type === 'viewport.changed') {
100
+ next = {
101
+ ...next,
102
+ effectiveViewMode: resolveEffectiveViewMode(next.viewMode, action.width),
103
+ };
104
+ return withClampedTop(next, model, viewportHeight);
105
+ }
106
+
107
+ if (action.type === 'view.setMode') {
108
+ next = {
109
+ ...next,
110
+ viewMode: action.mode,
111
+ effectiveViewMode: resolveEffectiveViewMode(action.mode, viewportWidth),
112
+ };
113
+ return withClampedTop(next, model, viewportHeight);
114
+ }
115
+
116
+ if (action.type === 'nav.scroll') {
117
+ next = {
118
+ ...next,
119
+ topRow: next.topRow + action.delta,
120
+ };
121
+ return withClampedTop(next, model, viewportHeight);
122
+ }
123
+
124
+ if (action.type === 'nav.page') {
125
+ next = {
126
+ ...next,
127
+ topRow: next.topRow + action.delta * Math.max(1, action.pageSize),
128
+ };
129
+ return withClampedTop(next, model, viewportHeight);
130
+ }
131
+
132
+ if (action.type === 'nav.gotoFile') {
133
+ const fileIndex = clampIndex(action.fileIndex, model.diff.files.length);
134
+ next = {
135
+ ...next,
136
+ activeFileIndex: fileIndex,
137
+ activeHunkIndex: firstHunkIndexForFile(model, fileIndex),
138
+ topRow: resolveTopForFile(model, fileIndex),
139
+ finderOpen: false,
140
+ };
141
+ return withClampedTop(next, model, viewportHeight);
142
+ }
143
+
144
+ if (action.type === 'nav.gotoHunk') {
145
+ const hunkIndex = clampIndex(action.hunkIndex, model.hunkStartRows.length);
146
+ const hunkRow = model.rows.find(
147
+ (row) => row.kind === 'hunk-header' && row.hunkIndex === hunkIndex,
148
+ );
149
+ next = {
150
+ ...next,
151
+ activeHunkIndex: hunkIndex,
152
+ activeFileIndex: hunkRow?.fileIndex ?? next.activeFileIndex,
153
+ topRow: resolveTopForHunk(model, hunkIndex),
154
+ finderOpen: false,
155
+ };
156
+ return withClampedTop(next, model, viewportHeight);
157
+ }
158
+
159
+ if (action.type === 'finder.open') {
160
+ next = {
161
+ ...next,
162
+ finderOpen: true,
163
+ finderResults: buildFinderResults(model, next.finderQuery),
164
+ finderSelectedIndex: 0,
165
+ };
166
+ return withClampedTop(next, model, viewportHeight);
167
+ }
168
+
169
+ if (action.type === 'finder.close') {
170
+ next = {
171
+ ...next,
172
+ finderOpen: false,
173
+ };
174
+ return withClampedTop(next, model, viewportHeight);
175
+ }
176
+
177
+ if (action.type === 'finder.query') {
178
+ const results = buildFinderResults(model, action.query);
179
+ next = {
180
+ ...next,
181
+ finderOpen: true,
182
+ finderQuery: action.query,
183
+ finderResults: results,
184
+ finderSelectedIndex: 0,
185
+ };
186
+ return withClampedTop(next, model, viewportHeight);
187
+ }
188
+
189
+ if (action.type === 'finder.move') {
190
+ const selected = clampIndex(next.finderSelectedIndex + action.delta, next.finderResults.length);
191
+ next = {
192
+ ...next,
193
+ finderSelectedIndex: selected,
194
+ };
195
+ return withClampedTop(next, model, viewportHeight);
196
+ }
197
+
198
+ if (action.type === 'finder.accept') {
199
+ if (next.finderResults.length === 0) {
200
+ return withClampedTop(next, model, viewportHeight);
201
+ }
202
+ const selection =
203
+ next.finderResults[clampIndex(next.finderSelectedIndex, next.finderResults.length)] ??
204
+ next.finderResults[0]!;
205
+ next = {
206
+ ...next,
207
+ activeFileIndex: selection.fileIndex,
208
+ activeHunkIndex: firstHunkIndexForFile(model, selection.fileIndex),
209
+ topRow: resolveTopForFile(model, selection.fileIndex),
210
+ finderOpen: false,
211
+ };
212
+ return withClampedTop(next, model, viewportHeight);
213
+ }
214
+
215
+ if (action.type === 'search.set') {
216
+ next = {
217
+ ...next,
218
+ searchQuery: action.query,
219
+ };
220
+ return withClampedTop(next, model, viewportHeight);
221
+ }
222
+
223
+ return withClampedTop(next, model, viewportHeight);
224
+ }
@@ -0,0 +1,236 @@
1
+ import type { DiffCoverageReason, DiffMode, NormalizedDiff } from '../diff/types.ts';
2
+
3
+ export type DiffUiViewMode = 'auto' | 'split' | 'unified';
4
+ type DiffUiEffectiveViewMode = 'split' | 'unified';
5
+ export type DiffUiSyntaxMode = 'auto' | 'on' | 'off';
6
+ export type DiffUiWordDiffMode = 'auto' | 'on' | 'off';
7
+
8
+ interface DiffUiBudget {
9
+ readonly maxFiles: number;
10
+ readonly maxHunks: number;
11
+ readonly maxLines: number;
12
+ readonly maxBytes: number;
13
+ readonly maxRuntimeMs: number;
14
+ }
15
+
16
+ export interface DiffUiCliOptions {
17
+ readonly cwd: string;
18
+ readonly mode: DiffMode;
19
+ readonly baseRef: string | null;
20
+ readonly headRef: string | null;
21
+ readonly includeGenerated: boolean;
22
+ readonly includeBinary: boolean;
23
+ readonly noRenames: boolean;
24
+ readonly renameLimit: number | null;
25
+ readonly viewMode: DiffUiViewMode;
26
+ readonly syntaxMode: DiffUiSyntaxMode;
27
+ readonly wordDiffMode: DiffUiWordDiffMode;
28
+ readonly color: boolean;
29
+ readonly pager: boolean;
30
+ readonly watch: boolean;
31
+ readonly jsonEvents: boolean;
32
+ readonly rpcStdio: boolean;
33
+ readonly snapshot: boolean;
34
+ readonly width: number | null;
35
+ readonly height: number | null;
36
+ readonly theme: string | null;
37
+ readonly budget: DiffUiBudget;
38
+ }
39
+
40
+ export type DiffUiRowKind =
41
+ | 'file-header'
42
+ | 'hunk-header'
43
+ | 'code-context'
44
+ | 'code-add'
45
+ | 'code-del'
46
+ | 'notice';
47
+
48
+ export interface DiffUiVirtualRow {
49
+ readonly kind: DiffUiRowKind;
50
+ readonly unified: string;
51
+ readonly left: string;
52
+ readonly right: string;
53
+ readonly fileId: string | null;
54
+ readonly hunkId: string | null;
55
+ readonly fileIndex: number | null;
56
+ readonly hunkIndex: number | null;
57
+ readonly language: string | null;
58
+ readonly oldLine: number | null;
59
+ readonly newLine: number | null;
60
+ }
61
+
62
+ export interface DiffUiModel {
63
+ readonly diff: NormalizedDiff;
64
+ readonly rows: readonly DiffUiVirtualRow[];
65
+ readonly fileStartRows: readonly number[];
66
+ readonly hunkStartRows: readonly number[];
67
+ }
68
+
69
+ export interface DiffUiFinderResult {
70
+ readonly fileIndex: number;
71
+ readonly fileId: string;
72
+ readonly path: string;
73
+ readonly score: number;
74
+ }
75
+
76
+ export interface DiffUiState {
77
+ readonly viewMode: DiffUiViewMode;
78
+ readonly effectiveViewMode: DiffUiEffectiveViewMode;
79
+ readonly topRow: number;
80
+ readonly activeFileIndex: number;
81
+ readonly activeHunkIndex: number;
82
+ readonly finderOpen: boolean;
83
+ readonly finderQuery: string;
84
+ readonly finderSelectedIndex: number;
85
+ readonly finderResults: readonly DiffUiFinderResult[];
86
+ readonly searchQuery: string;
87
+ }
88
+
89
+ export type DiffUiStateAction =
90
+ | {
91
+ readonly type: 'viewport.changed';
92
+ readonly width: number;
93
+ }
94
+ | {
95
+ readonly type: 'view.setMode';
96
+ readonly mode: DiffUiViewMode;
97
+ }
98
+ | {
99
+ readonly type: 'nav.scroll';
100
+ readonly delta: number;
101
+ }
102
+ | {
103
+ readonly type: 'nav.page';
104
+ readonly delta: number;
105
+ readonly pageSize: number;
106
+ }
107
+ | {
108
+ readonly type: 'nav.gotoFile';
109
+ readonly fileIndex: number;
110
+ }
111
+ | {
112
+ readonly type: 'nav.gotoHunk';
113
+ readonly hunkIndex: number;
114
+ }
115
+ | {
116
+ readonly type: 'finder.open';
117
+ }
118
+ | {
119
+ readonly type: 'finder.close';
120
+ }
121
+ | {
122
+ readonly type: 'finder.query';
123
+ readonly query: string;
124
+ }
125
+ | {
126
+ readonly type: 'finder.move';
127
+ readonly delta: number;
128
+ }
129
+ | {
130
+ readonly type: 'finder.accept';
131
+ }
132
+ | {
133
+ readonly type: 'search.set';
134
+ readonly query: string;
135
+ };
136
+
137
+ export type DiffUiCommand =
138
+ | {
139
+ readonly type: 'view.setMode';
140
+ readonly mode: DiffUiViewMode;
141
+ }
142
+ | {
143
+ readonly type: 'nav.scroll';
144
+ readonly delta: number;
145
+ }
146
+ | {
147
+ readonly type: 'nav.page';
148
+ readonly delta: number;
149
+ }
150
+ | {
151
+ readonly type: 'nav.gotoFile';
152
+ readonly index: number;
153
+ }
154
+ | {
155
+ readonly type: 'nav.gotoHunk';
156
+ readonly index: number;
157
+ }
158
+ | {
159
+ readonly type: 'finder.open';
160
+ }
161
+ | {
162
+ readonly type: 'finder.close';
163
+ }
164
+ | {
165
+ readonly type: 'finder.query';
166
+ readonly query: string;
167
+ }
168
+ | {
169
+ readonly type: 'finder.move';
170
+ readonly delta: number;
171
+ }
172
+ | {
173
+ readonly type: 'finder.accept';
174
+ }
175
+ | {
176
+ readonly type: 'search.set';
177
+ readonly query: string;
178
+ }
179
+ | {
180
+ readonly type: 'session.quit';
181
+ };
182
+
183
+ export type DiffUiEvent =
184
+ | {
185
+ readonly type: 'diff.loaded';
186
+ readonly files: number;
187
+ readonly hunks: number;
188
+ readonly lines: number;
189
+ readonly coverageReason: DiffCoverageReason;
190
+ }
191
+ | {
192
+ readonly type: 'state.changed';
193
+ readonly state: DiffUiState;
194
+ }
195
+ | {
196
+ readonly type: 'render.completed';
197
+ readonly rows: number;
198
+ readonly width: number;
199
+ readonly height: number;
200
+ readonly view: DiffUiEffectiveViewMode;
201
+ }
202
+ | {
203
+ readonly type: 'warning';
204
+ readonly message: string;
205
+ }
206
+ | {
207
+ readonly type: 'session.quit';
208
+ };
209
+
210
+ export interface DiffUiRenderTheme {
211
+ readonly headerAnsi: string;
212
+ readonly footerAnsi: string;
213
+ readonly fileHeaderAnsi: string;
214
+ readonly hunkHeaderAnsi: string;
215
+ readonly contextAnsi: string;
216
+ readonly addAnsi: string;
217
+ readonly delAnsi: string;
218
+ readonly noticeAnsi: string;
219
+ readonly gutterAnsi: string;
220
+ readonly resetAnsi: string;
221
+ readonly syntaxKeywordAnsi: string;
222
+ readonly syntaxStringAnsi: string;
223
+ readonly syntaxCommentAnsi: string;
224
+ readonly syntaxNumberAnsi: string;
225
+ }
226
+
227
+ export interface DiffUiRenderOutput {
228
+ readonly lines: readonly string[];
229
+ readonly state: DiffUiState;
230
+ }
231
+
232
+ export interface DiffUiRunOutput {
233
+ readonly exitCode: number;
234
+ readonly events: readonly DiffUiEvent[];
235
+ readonly renderedLines: readonly string[];
236
+ }
@@ -1,13 +1,14 @@
1
1
  import type { ProjectPaneSnapshot } from '../mux/harness-core-ui.ts';
2
2
  import type { CommandMenuState } from '../mux/live-mux/command-menu.ts';
3
3
  import type { LeftNavSelection } from '../mux/live-mux/left-nav.ts';
4
+ import type { LinePromptInputState } from '../mux/live-mux/modal-input-reducers.ts';
4
5
  import type { PaneSelection, PaneSelectionDrag } from '../mux/live-mux/selection.ts';
5
6
  import type { createNewThreadPromptState } from '../mux/new-thread-prompt.ts';
6
7
  import type { TaskComposerBuffer } from '../mux/task-composer.ts';
7
8
  import type { TaskFocusedPaneView } from '../mux/task-focused-pane.ts';
8
9
  import type { buildWorkspaceRailViewRows } from '../mux/workspace-rail-model.ts';
9
10
 
10
- type MainPaneMode = 'conversation' | 'project' | 'home';
11
+ type MainPaneMode = 'conversation' | 'project' | 'home' | 'nim';
11
12
 
12
13
  export interface ConversationTitleEditState {
13
14
  conversationId: string;
@@ -25,19 +26,20 @@ export interface RepositoryPromptState {
25
26
  readonly error: string | null;
26
27
  }
27
28
 
28
- export interface ApiKeyPromptState {
29
+ interface ApiKeyPromptState {
29
30
  readonly keyName: string;
30
31
  readonly displayName: string;
31
32
  readonly value: string;
32
33
  readonly error: string | null;
33
34
  readonly hasExistingValue: boolean;
35
+ readonly lineInputState?: LinePromptInputState;
34
36
  }
35
37
 
36
38
  export interface TaskEditorPromptState {
37
39
  mode: 'create' | 'edit';
38
40
  taskId: string | null;
39
41
  title: string;
40
- description: string;
42
+ body: string;
41
43
  repositoryIds: readonly string[];
42
44
  repositoryIndex: number;
43
45
  fieldIndex: 0 | 1 | 2;
@@ -60,7 +62,8 @@ interface WorkspaceModelInit {
60
62
  latestTaskPaneView: TaskFocusedPaneView;
61
63
  taskDraftComposer: TaskComposerBuffer;
62
64
  repositoriesCollapsed: boolean;
63
- shortcutsCollapsed: boolean;
65
+ shortcutsCollapsed?: boolean;
66
+ showDebugBar?: boolean;
64
67
  }
65
68
 
66
69
  export class WorkspaceModel {
@@ -98,9 +101,12 @@ export class WorkspaceModel {
98
101
  paneDividerDragActive = false;
99
102
  previousSelectionRows: readonly number[] = [];
100
103
  latestRailViewRows: ReturnType<typeof buildWorkspaceRailViewRows> = [];
104
+ visibleGitHubDirectoryIds = new Set<string>();
105
+ expandedGitHubDirectoryIds = new Set<string>();
101
106
 
102
107
  repositoriesCollapsed: boolean;
103
108
  shortcutsCollapsed: boolean;
109
+ showDebugBar: boolean;
104
110
 
105
111
  constructor(init: WorkspaceModelInit) {
106
112
  this.activeDirectoryId = init.activeDirectoryId;
@@ -108,7 +114,8 @@ export class WorkspaceModel {
108
114
  this.latestTaskPaneView = init.latestTaskPaneView;
109
115
  this.taskDraftComposer = init.taskDraftComposer;
110
116
  this.repositoriesCollapsed = init.repositoriesCollapsed;
111
- this.shortcutsCollapsed = init.shortcutsCollapsed;
117
+ this.shortcutsCollapsed = init.shortcutsCollapsed ?? false;
118
+ this.showDebugBar = init.showDebugBar ?? false;
112
119
  }
113
120
 
114
121
  selectLeftNavHome(): void {
@@ -117,6 +124,18 @@ export class WorkspaceModel {
117
124
  };
118
125
  }
119
126
 
127
+ selectLeftNavNim(): void {
128
+ this.leftNavSelection = {
129
+ kind: 'nim',
130
+ };
131
+ }
132
+
133
+ selectLeftNavTasks(): void {
134
+ this.leftNavSelection = {
135
+ kind: 'tasks',
136
+ };
137
+ }
138
+
120
139
  selectLeftNavRepository(repositoryGroupId: string): void {
121
140
  this.activeRepositorySelectionId = repositoryGroupId;
122
141
  this.leftNavSelection = {
@@ -133,6 +152,14 @@ export class WorkspaceModel {
133
152
  };
134
153
  }
135
154
 
155
+ selectLeftNavGitHub(directoryId: string, repositoryGroupId: string): void {
156
+ this.activeRepositorySelectionId = repositoryGroupId;
157
+ this.leftNavSelection = {
158
+ kind: 'github',
159
+ directoryId,
160
+ };
161
+ }
162
+
136
163
  selectLeftNavConversation(sessionId: string): void {
137
164
  this.leftNavSelection = {
138
165
  kind: 'conversation',
@@ -150,6 +177,16 @@ export class WorkspaceModel {
150
177
  this.projectPaneScrollTop = 0;
151
178
  }
152
179
 
180
+ enterGitHubPane(directoryId: string, repositoryGroupId: string): void {
181
+ this.activeDirectoryId = directoryId;
182
+ this.selectLeftNavGitHub(directoryId, repositoryGroupId);
183
+ this.mainPaneMode = 'project';
184
+ this.homePaneDragState = null;
185
+ this.taskPaneTaskEditClickState = null;
186
+ this.taskPaneRepositoryEditClickState = null;
187
+ this.projectPaneScrollTop = 0;
188
+ }
189
+
153
190
  enterHomePane(): void {
154
191
  this.mainPaneMode = 'home';
155
192
  this.selectLeftNavHome();
@@ -162,4 +199,30 @@ export class WorkspaceModel {
162
199
  this.taskPaneRepositoryEditClickState = null;
163
200
  this.homePaneDragState = null;
164
201
  }
202
+
203
+ enterNimPane(): void {
204
+ this.mainPaneMode = 'nim';
205
+ this.selectLeftNavNim();
206
+ this.projectPaneSnapshot = null;
207
+ this.projectPaneScrollTop = 0;
208
+ this.taskPaneScrollTop = 0;
209
+ this.taskPaneNotice = null;
210
+ this.taskRepositoryDropdownOpen = false;
211
+ this.taskPaneTaskEditClickState = null;
212
+ this.taskPaneRepositoryEditClickState = null;
213
+ this.homePaneDragState = null;
214
+ }
215
+
216
+ enterTasksPane(): void {
217
+ this.mainPaneMode = 'home';
218
+ this.selectLeftNavTasks();
219
+ this.projectPaneSnapshot = null;
220
+ this.projectPaneScrollTop = 0;
221
+ this.taskPaneScrollTop = 0;
222
+ this.taskPaneNotice = null;
223
+ this.taskRepositoryDropdownOpen = false;
224
+ this.taskPaneTaskEditClickState = null;
225
+ this.taskPaneRepositoryEditClickState = null;
226
+ this.homePaneDragState = null;
227
+ }
165
228
  }