@jmoyers/harness 0.1.10 → 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 (239) hide show
  1. package/README.md +31 -35
  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/{src/ui/modals/manager.ts → packages/harness-ui/src/modal-manager.ts} +94 -64
  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 -3721
  38. package/scripts/control-plane-daemon.ts +24 -2
  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 -3007
  43. package/scripts/nim-tui-smoke.ts +748 -0
  44. package/src/cli/auth/runtime.ts +948 -0
  45. package/src/cli/default-gateway-pointer.ts +193 -0
  46. package/src/cli/gateway/runtime.ts +1872 -0
  47. package/src/cli/parsing/flags.ts +23 -0
  48. package/src/cli/parsing/session.ts +42 -0
  49. package/src/cli/runtime/context.ts +193 -0
  50. package/src/cli/runtime-app/application.ts +392 -0
  51. package/src/cli/runtime-infra/gateway-control.ts +729 -0
  52. package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
  53. package/src/cli/workflows/runtime.ts +965 -0
  54. package/src/clients/tui/left-rail-interactions.ts +519 -0
  55. package/src/clients/tui/main-pane-interactions.ts +509 -0
  56. package/src/clients/tui/modal-input-routing.ts +71 -0
  57. package/src/clients/tui/render-snapshot-adapter.ts +88 -0
  58. package/src/clients/web/synced-selectors.ts +132 -0
  59. package/src/codex/live-session.ts +82 -29
  60. package/src/config/config-core.ts +361 -10
  61. package/src/config/harness-paths.ts +4 -7
  62. package/src/config/harness-runtime-migration.ts +142 -19
  63. package/src/config/harness.config.template.jsonc +33 -0
  64. package/src/config/secrets-core.ts +92 -4
  65. package/src/control-plane/agent-realtime-api.ts +82 -427
  66. package/src/control-plane/prompt/thread-title-namer.ts +49 -23
  67. package/src/control-plane/session-summary.ts +10 -81
  68. package/src/control-plane/status/reducer-base.ts +12 -12
  69. package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
  70. package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
  71. package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
  72. package/src/control-plane/stream-client.ts +12 -2
  73. package/src/control-plane/stream-command-parser.ts +83 -143
  74. package/src/control-plane/stream-protocol.ts +53 -37
  75. package/src/control-plane/stream-server-background.ts +18 -2
  76. package/src/control-plane/stream-server-command.ts +376 -69
  77. package/src/control-plane/stream-server-session-runtime.ts +3 -2
  78. package/src/control-plane/stream-server.ts +943 -80
  79. package/src/control-plane/stream-session-runtime-types.ts +41 -0
  80. package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
  81. package/src/core/state/observed-stream-cursor.ts +43 -0
  82. package/src/core/state/synced-observed-state.ts +273 -0
  83. package/src/core/store/harness-synced-store.ts +81 -0
  84. package/src/diff/budget.ts +136 -0
  85. package/src/diff/build.ts +289 -0
  86. package/src/diff/chunker.ts +146 -0
  87. package/src/diff/git-invoke.ts +315 -0
  88. package/src/diff/git-parse.ts +472 -0
  89. package/src/diff/hash.ts +70 -0
  90. package/src/diff/index.ts +24 -0
  91. package/src/diff/normalize.ts +134 -0
  92. package/src/diff/types.ts +178 -0
  93. package/src/diff-ui/args.ts +346 -0
  94. package/src/diff-ui/commands.ts +123 -0
  95. package/src/diff-ui/finder.ts +94 -0
  96. package/src/diff-ui/highlight.ts +127 -0
  97. package/src/diff-ui/index.ts +2 -0
  98. package/src/diff-ui/model.ts +141 -0
  99. package/src/diff-ui/pager.ts +412 -0
  100. package/src/diff-ui/render.ts +337 -0
  101. package/src/diff-ui/runtime.ts +379 -0
  102. package/src/diff-ui/state.ts +224 -0
  103. package/src/diff-ui/types.ts +236 -0
  104. package/src/domain/conversations.ts +11 -7
  105. package/src/domain/workspace.ts +76 -4
  106. package/src/mux/control-plane-op-queue.ts +93 -7
  107. package/src/mux/conversation-rail.ts +28 -71
  108. package/src/mux/dual-pane-core.ts +13 -13
  109. package/src/mux/harness-core-ui.ts +313 -42
  110. package/src/mux/input-shortcuts.ts +22 -112
  111. package/src/mux/keybinding-catalog.ts +340 -0
  112. package/src/mux/keybinding-registry.ts +103 -0
  113. package/src/mux/live-mux/command-menu-open-in.ts +280 -0
  114. package/src/mux/live-mux/command-menu.ts +167 -4
  115. package/src/mux/live-mux/conversation-state.ts +13 -0
  116. package/src/mux/live-mux/directory-resolution.ts +1 -1
  117. package/src/mux/live-mux/git-parsing.ts +16 -0
  118. package/src/mux/live-mux/git-snapshot.ts +33 -2
  119. package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
  120. package/src/mux/live-mux/home-pane-drop.ts +1 -1
  121. package/src/mux/live-mux/home-pane-pointer.ts +10 -0
  122. package/src/mux/live-mux/input-forwarding.ts +59 -2
  123. package/src/mux/live-mux/left-nav-activation.ts +124 -7
  124. package/src/mux/live-mux/left-nav.ts +35 -0
  125. package/src/mux/live-mux/link-click.ts +292 -0
  126. package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
  127. package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
  128. package/src/mux/live-mux/modal-input-reducers.ts +106 -8
  129. package/src/mux/live-mux/modal-overlays.ts +210 -31
  130. package/src/mux/live-mux/modal-pointer.ts +3 -7
  131. package/src/mux/live-mux/modal-prompt-handlers.ts +107 -1
  132. package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
  133. package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
  134. package/src/mux/live-mux/pointer-routing.ts +5 -2
  135. package/src/mux/live-mux/project-pane-pointer.ts +8 -0
  136. package/src/mux/live-mux/rail-layout.ts +33 -30
  137. package/src/mux/live-mux/release-notes.ts +383 -0
  138. package/src/mux/live-mux/render-trace-analysis.ts +52 -7
  139. package/src/mux/live-mux/repository-folding.ts +3 -0
  140. package/src/mux/live-mux/selection.ts +0 -4
  141. package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
  142. package/src/mux/project-pane-github-review.ts +271 -0
  143. package/src/mux/render-frame.ts +4 -0
  144. package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
  145. package/src/mux/task-composer.ts +21 -14
  146. package/src/mux/task-focused-pane.ts +118 -117
  147. package/src/mux/task-screen-keybindings.ts +19 -82
  148. package/src/mux/workspace-rail-model.ts +270 -104
  149. package/src/mux/workspace-rail.ts +45 -22
  150. package/src/pty/session-broker.ts +1 -1
  151. package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
  152. package/src/services/control-plane.ts +50 -32
  153. package/src/services/conversation-lifecycle.ts +118 -87
  154. package/src/services/conversation-startup-hydration.ts +20 -12
  155. package/src/services/directory-hydration.ts +21 -16
  156. package/src/services/event-persistence.ts +7 -0
  157. package/src/services/left-rail-pointer-handler.ts +329 -0
  158. package/src/services/mux-ui-state-persistence.ts +5 -1
  159. package/src/services/recording.ts +34 -26
  160. package/src/services/runtime-command-menu-agent-tools.ts +1 -1
  161. package/src/services/runtime-control-actions.ts +79 -61
  162. package/src/services/runtime-control-plane-ops.ts +122 -83
  163. package/src/services/runtime-conversation-actions.ts +40 -26
  164. package/src/services/runtime-conversation-activation.ts +82 -30
  165. package/src/services/runtime-conversation-starter.ts +80 -48
  166. package/src/services/runtime-conversation-title-edit.ts +91 -80
  167. package/src/services/runtime-envelope-handler.ts +107 -105
  168. package/src/services/runtime-git-state.ts +42 -29
  169. package/src/services/runtime-layout-resize.ts +3 -1
  170. package/src/services/runtime-left-rail-render.ts +99 -63
  171. package/src/services/runtime-nim-cli-session.ts +438 -0
  172. package/src/services/runtime-nim-session.ts +705 -0
  173. package/src/services/runtime-nim-tool-bridge.ts +141 -0
  174. package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
  175. package/src/services/runtime-process-wiring.ts +29 -36
  176. package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
  177. package/src/services/runtime-render-flush.ts +63 -70
  178. package/src/services/runtime-render-lifecycle.ts +65 -64
  179. package/src/services/runtime-render-orchestrator.ts +55 -45
  180. package/src/services/runtime-render-pipeline.ts +106 -103
  181. package/src/services/runtime-render-state.ts +62 -49
  182. package/src/services/runtime-repository-actions.ts +97 -70
  183. package/src/services/runtime-right-pane-render.ts +80 -53
  184. package/src/services/runtime-shutdown.ts +38 -35
  185. package/src/services/runtime-stream-subscriptions.ts +35 -27
  186. package/src/services/runtime-task-composer-persistence.ts +71 -59
  187. package/src/services/runtime-task-composer-snapshot.ts +14 -0
  188. package/src/services/runtime-task-editor-actions.ts +46 -29
  189. package/src/services/runtime-task-pane-actions.ts +220 -134
  190. package/src/services/runtime-task-pane-shortcuts.ts +323 -123
  191. package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
  192. package/src/services/runtime-workspace-observed-events.ts +33 -184
  193. package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
  194. package/src/services/session-diagnostics-store.ts +217 -0
  195. package/src/services/startup-background-resume.ts +26 -21
  196. package/src/services/startup-orchestrator.ts +16 -13
  197. package/src/services/startup-paint-tracker.ts +29 -21
  198. package/src/services/startup-persisted-conversation-queue.ts +19 -13
  199. package/src/services/startup-settled-gate.ts +25 -15
  200. package/src/services/startup-shutdown.ts +18 -22
  201. package/src/services/startup-state-hydration.ts +44 -34
  202. package/src/services/startup-visibility.ts +12 -4
  203. package/src/services/task-pane-selection-actions.ts +89 -72
  204. package/src/services/task-planning-hydration.ts +24 -18
  205. package/src/services/task-planning-observed-events.ts +50 -52
  206. package/src/services/workspace-observed-events.ts +66 -63
  207. package/src/storage/storage-lifecycle-core.ts +438 -0
  208. package/src/store/control-plane-store-normalize.ts +33 -242
  209. package/src/store/control-plane-store-types.ts +1 -35
  210. package/src/store/control-plane-store.ts +396 -56
  211. package/src/store/event-store.ts +397 -3
  212. package/src/terminal/snapshot-oracle.ts +207 -94
  213. package/src/ui/mux-theme.ts +112 -8
  214. package/src/ui/panes/home-gridfire.ts +40 -31
  215. package/src/ui/panes/home.ts +10 -2
  216. package/src/ui/panes/nim.ts +315 -0
  217. package/src/mux/live-mux/actions-task.ts +0 -115
  218. package/src/mux/live-mux/left-rail-actions.ts +0 -118
  219. package/src/mux/live-mux/left-rail-conversation-click.ts +0 -82
  220. package/src/mux/live-mux/left-rail-pointer.ts +0 -74
  221. package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
  222. package/src/services/runtime-directory-actions.ts +0 -164
  223. package/src/services/runtime-input-pipeline.ts +0 -50
  224. package/src/services/runtime-input-router.ts +0 -189
  225. package/src/services/runtime-main-pane-input.ts +0 -230
  226. package/src/services/runtime-modal-input.ts +0 -119
  227. package/src/services/runtime-navigation-input.ts +0 -197
  228. package/src/services/runtime-rail-input.ts +0 -278
  229. package/src/services/runtime-task-pane.ts +0 -62
  230. package/src/services/runtime-workspace-actions.ts +0 -158
  231. package/src/ui/conversation-input-forwarder.ts +0 -114
  232. package/src/ui/conversation-selection-input.ts +0 -103
  233. package/src/ui/global-shortcut-input.ts +0 -89
  234. package/src/ui/input.ts +0 -238
  235. package/src/ui/kit.ts +0 -509
  236. package/src/ui/left-nav-input.ts +0 -80
  237. package/src/ui/left-rail-pointer-input.ts +0 -148
  238. package/src/ui/repository-fold-input.ts +0 -91
  239. 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
+ }
@@ -290,6 +290,8 @@ export class ConversationManager {
290
290
 
291
291
  upsertFromPersistedRecord(input: UpsertPersistedConversationInput): ConversationState {
292
292
  const { record } = input;
293
+ const existing = this.conversationsBySessionId.get(record.conversationId);
294
+ const preserveLiveRuntime = existing?.live === true;
293
295
  const conversation = input.ensureConversation(record.conversationId, {
294
296
  directoryId: record.directoryId,
295
297
  title: record.title,
@@ -299,14 +301,16 @@ export class ConversationManager {
299
301
  conversation.scope.tenantId = record.tenantId;
300
302
  conversation.scope.userId = record.userId;
301
303
  conversation.scope.workspaceId = record.workspaceId;
302
- const runtimeStatusModel = record.runtimeStatusModel;
303
- conversation.status = record.runtimeStatus;
304
- conversation.statusModel = runtimeStatusModel;
305
- conversation.attentionReason = runtimeStatusModel?.attentionReason ?? null;
306
- conversation.lastKnownWork = runtimeStatusModel?.lastKnownWork ?? null;
307
- conversation.lastKnownWorkAt = runtimeStatusModel?.lastKnownWorkAt ?? null;
304
+ if (!preserveLiveRuntime) {
305
+ const runtimeStatusModel = record.runtimeStatusModel;
306
+ conversation.status = record.runtimeStatus;
307
+ conversation.statusModel = runtimeStatusModel;
308
+ conversation.attentionReason = runtimeStatusModel?.attentionReason ?? null;
309
+ conversation.lastKnownWork = runtimeStatusModel?.lastKnownWork ?? null;
310
+ conversation.lastKnownWorkAt = runtimeStatusModel?.lastKnownWorkAt ?? null;
311
+ }
308
312
  // Persisted runtime flags are advisory; session.list is authoritative for live sessions.
309
- conversation.live = false;
313
+ conversation.live = preserveLiveRuntime ? true : false;
310
314
  return conversation;
311
315
  }
312
316
 
@@ -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,11 +26,20 @@ export interface RepositoryPromptState {
25
26
  readonly error: string | null;
26
27
  }
27
28
 
29
+ interface ApiKeyPromptState {
30
+ readonly keyName: string;
31
+ readonly displayName: string;
32
+ readonly value: string;
33
+ readonly error: string | null;
34
+ readonly hasExistingValue: boolean;
35
+ readonly lineInputState?: LinePromptInputState;
36
+ }
37
+
28
38
  export interface TaskEditorPromptState {
29
39
  mode: 'create' | 'edit';
30
40
  taskId: string | null;
31
41
  title: string;
32
- description: string;
42
+ body: string;
33
43
  repositoryIds: readonly string[];
34
44
  repositoryIndex: number;
35
45
  fieldIndex: 0 | 1 | 2;
@@ -52,7 +62,8 @@ interface WorkspaceModelInit {
52
62
  latestTaskPaneView: TaskFocusedPaneView;
53
63
  taskDraftComposer: TaskComposerBuffer;
54
64
  repositoriesCollapsed: boolean;
55
- shortcutsCollapsed: boolean;
65
+ shortcutsCollapsed?: boolean;
66
+ showDebugBar?: boolean;
56
67
  }
57
68
 
58
69
  export class WorkspaceModel {
@@ -80,6 +91,7 @@ export class WorkspaceModel {
80
91
  selectionDrag: PaneSelectionDrag | null = null;
81
92
  selectionPinnedFollowOutput: boolean | null = null;
82
93
  repositoryPrompt: RepositoryPromptState | null = null;
94
+ apiKeyPrompt: ApiKeyPromptState | null = null;
83
95
  commandMenu: CommandMenuState | null = null;
84
96
  newThreadPrompt: ReturnType<typeof createNewThreadPromptState> | null = null;
85
97
  addDirectoryPrompt: { value: string; error: string | null } | null = null;
@@ -89,9 +101,12 @@ export class WorkspaceModel {
89
101
  paneDividerDragActive = false;
90
102
  previousSelectionRows: readonly number[] = [];
91
103
  latestRailViewRows: ReturnType<typeof buildWorkspaceRailViewRows> = [];
104
+ visibleGitHubDirectoryIds = new Set<string>();
105
+ expandedGitHubDirectoryIds = new Set<string>();
92
106
 
93
107
  repositoriesCollapsed: boolean;
94
108
  shortcutsCollapsed: boolean;
109
+ showDebugBar: boolean;
95
110
 
96
111
  constructor(init: WorkspaceModelInit) {
97
112
  this.activeDirectoryId = init.activeDirectoryId;
@@ -99,7 +114,8 @@ export class WorkspaceModel {
99
114
  this.latestTaskPaneView = init.latestTaskPaneView;
100
115
  this.taskDraftComposer = init.taskDraftComposer;
101
116
  this.repositoriesCollapsed = init.repositoriesCollapsed;
102
- this.shortcutsCollapsed = init.shortcutsCollapsed;
117
+ this.shortcutsCollapsed = init.shortcutsCollapsed ?? false;
118
+ this.showDebugBar = init.showDebugBar ?? false;
103
119
  }
104
120
 
105
121
  selectLeftNavHome(): void {
@@ -108,6 +124,18 @@ export class WorkspaceModel {
108
124
  };
109
125
  }
110
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
+
111
139
  selectLeftNavRepository(repositoryGroupId: string): void {
112
140
  this.activeRepositorySelectionId = repositoryGroupId;
113
141
  this.leftNavSelection = {
@@ -124,6 +152,14 @@ export class WorkspaceModel {
124
152
  };
125
153
  }
126
154
 
155
+ selectLeftNavGitHub(directoryId: string, repositoryGroupId: string): void {
156
+ this.activeRepositorySelectionId = repositoryGroupId;
157
+ this.leftNavSelection = {
158
+ kind: 'github',
159
+ directoryId,
160
+ };
161
+ }
162
+
127
163
  selectLeftNavConversation(sessionId: string): void {
128
164
  this.leftNavSelection = {
129
165
  kind: 'conversation',
@@ -141,6 +177,16 @@ export class WorkspaceModel {
141
177
  this.projectPaneScrollTop = 0;
142
178
  }
143
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
+
144
190
  enterHomePane(): void {
145
191
  this.mainPaneMode = 'home';
146
192
  this.selectLeftNavHome();
@@ -153,4 +199,30 @@ export class WorkspaceModel {
153
199
  this.taskPaneRepositoryEditClickState = null;
154
200
  this.homePaneDragState = null;
155
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
+ }
156
228
  }