@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
@@ -1,4 +1,8 @@
1
- import { reduceLinePromptInput } from './modal-input-reducers.ts';
1
+ import {
2
+ createLinePromptInputState,
3
+ reduceLinePromptInput,
4
+ type LinePromptInputState,
5
+ } from './modal-input-reducers.ts';
2
6
 
3
7
  interface AddDirectoryPromptState {
4
8
  value: string;
@@ -12,6 +16,15 @@ interface RepositoryPromptState {
12
16
  readonly error: string | null;
13
17
  }
14
18
 
19
+ interface ApiKeyPromptState {
20
+ readonly keyName: string;
21
+ readonly displayName: string;
22
+ readonly value: string;
23
+ readonly error: string | null;
24
+ readonly hasExistingValue: boolean;
25
+ readonly lineInputState?: LinePromptInputState;
26
+ }
27
+
15
28
  interface HandleAddDirectoryPromptInputOptions {
16
29
  input: Buffer;
17
30
  prompt: AddDirectoryPromptState | null;
@@ -36,6 +49,18 @@ interface HandleRepositoryPromptInputOptions {
36
49
  upsertRepositoryByRemoteUrl: (remoteUrl: string, existingRepositoryId?: string) => Promise<void>;
37
50
  }
38
51
 
52
+ interface HandleApiKeyPromptInputOptions {
53
+ input: Buffer;
54
+ prompt: ApiKeyPromptState | null;
55
+ isQuitShortcut: (input: Buffer) => boolean;
56
+ dismissOnOutsideClick: (input: Buffer, dismiss: () => void) => boolean;
57
+ setPrompt: (next: ApiKeyPromptState | null) => void;
58
+ markDirty: () => void;
59
+ persistApiKey: (keyName: string, value: string) => void;
60
+ }
61
+
62
+ const MOUSE_EVENT_PREFIX = Buffer.from('\u001b[<', 'utf8');
63
+
39
64
  export function handleAddDirectoryPromptInput(
40
65
  options: HandleAddDirectoryPromptInputOptions,
41
66
  ): boolean {
@@ -60,7 +85,9 @@ export function handleAddDirectoryPromptInput(
60
85
  markDirty();
61
86
  return true;
62
87
  }
88
+ const maybeMouseSequence = input.includes(MOUSE_EVENT_PREFIX);
63
89
  if (
90
+ maybeMouseSequence &&
64
91
  dismissOnOutsideClick(input, () => {
65
92
  setPrompt(null);
66
93
  markDirty();
@@ -123,7 +150,9 @@ export function handleRepositoryPromptInput(options: HandleRepositoryPromptInput
123
150
  markDirty();
124
151
  return true;
125
152
  }
153
+ const maybeMouseSequence = input.includes(MOUSE_EVENT_PREFIX);
126
154
  if (
155
+ maybeMouseSequence &&
127
156
  dismissOnOutsideClick(input, () => {
128
157
  setPrompt(null);
129
158
  markDirty();
@@ -185,3 +214,80 @@ export function handleRepositoryPromptInput(options: HandleRepositoryPromptInput
185
214
  markDirty();
186
215
  return true;
187
216
  }
217
+
218
+ export function handleApiKeyPromptInput(options: HandleApiKeyPromptInputOptions): boolean {
219
+ const {
220
+ input,
221
+ prompt,
222
+ isQuitShortcut,
223
+ dismissOnOutsideClick,
224
+ setPrompt,
225
+ markDirty,
226
+ persistApiKey,
227
+ } = options;
228
+ if (prompt === null) {
229
+ return false;
230
+ }
231
+ if (input.length === 1 && input[0] === 0x03) {
232
+ return false;
233
+ }
234
+ if (isQuitShortcut(input)) {
235
+ setPrompt(null);
236
+ markDirty();
237
+ return true;
238
+ }
239
+ const maybeMouseSequence = input.includes(MOUSE_EVENT_PREFIX);
240
+ if (
241
+ maybeMouseSequence &&
242
+ dismissOnOutsideClick(input, () => {
243
+ setPrompt(null);
244
+ markDirty();
245
+ })
246
+ ) {
247
+ return true;
248
+ }
249
+
250
+ const reduced = reduceLinePromptInput(
251
+ prompt.value,
252
+ input,
253
+ prompt.lineInputState ?? createLinePromptInputState(),
254
+ );
255
+ const value = reduced.value;
256
+ const lineInputState = reduced.lineInputState;
257
+ if (!reduced.submit) {
258
+ setPrompt({
259
+ ...prompt,
260
+ value,
261
+ error: null,
262
+ lineInputState,
263
+ });
264
+ markDirty();
265
+ return true;
266
+ }
267
+
268
+ const trimmed = value.trim();
269
+ if (trimmed.length === 0) {
270
+ setPrompt({
271
+ ...prompt,
272
+ value,
273
+ error: `${prompt.displayName.toLowerCase()} required`,
274
+ lineInputState,
275
+ });
276
+ markDirty();
277
+ return true;
278
+ }
279
+ try {
280
+ persistApiKey(prompt.keyName, trimmed);
281
+ setPrompt(null);
282
+ } catch (error: unknown) {
283
+ const message = error instanceof Error ? error.message : String(error);
284
+ setPrompt({
285
+ ...prompt,
286
+ value,
287
+ error: message,
288
+ lineInputState,
289
+ });
290
+ }
291
+ markDirty();
292
+ return true;
293
+ }
@@ -0,0 +1,111 @@
1
+ import type { ReleaseNotesPrompt } from './release-notes.ts';
2
+ import { RELEASE_NOTES_UPDATE_ACTION_ROW_OFFSET } from './modal-overlays.ts';
3
+
4
+ interface HandleReleaseNotesModalInputOptions {
5
+ readonly input: Buffer;
6
+ readonly prompt: ReleaseNotesPrompt | null;
7
+ readonly isQuitShortcut: (input: Buffer) => boolean;
8
+ readonly dismissOnOutsideClick: (
9
+ input: Buffer,
10
+ dismiss: () => void,
11
+ onInsidePointerPress?: (col: number, row: number) => boolean,
12
+ ) => boolean;
13
+ readonly buildReleaseNotesModalOverlay: () => { top: number } | null;
14
+ readonly setPrompt: (next: ReleaseNotesPrompt | null) => void;
15
+ readonly markDirty: () => void;
16
+ readonly onDismiss: (latestTag: string) => void;
17
+ readonly onNeverShowAgain: (latestTag: string) => void;
18
+ readonly onOpenLatest: (prompt: ReleaseNotesPrompt) => void;
19
+ readonly onUpdate: () => void;
20
+ }
21
+
22
+ function byteMatches(inputByte: number, lower: string): boolean {
23
+ const ascii = lower.charCodeAt(0);
24
+ return inputByte === ascii || inputByte === ascii - 32;
25
+ }
26
+
27
+ export function handleReleaseNotesModalInput(
28
+ options: HandleReleaseNotesModalInputOptions,
29
+ ): boolean {
30
+ const {
31
+ input,
32
+ prompt,
33
+ isQuitShortcut,
34
+ dismissOnOutsideClick,
35
+ buildReleaseNotesModalOverlay,
36
+ setPrompt,
37
+ markDirty,
38
+ onDismiss,
39
+ onNeverShowAgain,
40
+ onOpenLatest,
41
+ onUpdate,
42
+ } = options;
43
+ if (prompt === null) {
44
+ return false;
45
+ }
46
+ if (input.length === 1 && input[0] === 0x03) {
47
+ return false;
48
+ }
49
+ if (isQuitShortcut(input)) {
50
+ setPrompt(null);
51
+ onDismiss(prompt.latestTag);
52
+ markDirty();
53
+ return true;
54
+ }
55
+ if (
56
+ input.includes(0x3c) &&
57
+ dismissOnOutsideClick(
58
+ input,
59
+ () => {
60
+ setPrompt(null);
61
+ onDismiss(prompt.latestTag);
62
+ markDirty();
63
+ },
64
+ (_col, row) => {
65
+ const overlay = buildReleaseNotesModalOverlay();
66
+ if (overlay === null) {
67
+ return false;
68
+ }
69
+ if (row - 1 !== overlay.top + RELEASE_NOTES_UPDATE_ACTION_ROW_OFFSET) {
70
+ return false;
71
+ }
72
+ setPrompt(null);
73
+ onDismiss(prompt.latestTag);
74
+ onUpdate();
75
+ markDirty();
76
+ return true;
77
+ },
78
+ )
79
+ ) {
80
+ return true;
81
+ }
82
+
83
+ for (const byte of input) {
84
+ if (byte === 0x0d || byte === 0x0a) {
85
+ setPrompt(null);
86
+ onDismiss(prompt.latestTag);
87
+ markDirty();
88
+ return true;
89
+ }
90
+ if (byteMatches(byte, 'n')) {
91
+ setPrompt(null);
92
+ onNeverShowAgain(prompt.latestTag);
93
+ markDirty();
94
+ return true;
95
+ }
96
+ if (byteMatches(byte, 'u')) {
97
+ setPrompt(null);
98
+ onDismiss(prompt.latestTag);
99
+ onUpdate();
100
+ markDirty();
101
+ return true;
102
+ }
103
+ if (byteMatches(byte, 'o')) {
104
+ onOpenLatest(prompt);
105
+ markDirty();
106
+ return true;
107
+ }
108
+ }
109
+
110
+ return true;
111
+ }
@@ -12,9 +12,10 @@ interface TaskEditorPromptState extends TaskEditorPromptInputState {
12
12
  interface TaskEditorSubmitPayload {
13
13
  mode: 'create' | 'edit';
14
14
  taskId: string | null;
15
- repositoryId: string;
16
- title: string;
17
- description: string;
15
+ repositoryId: string | null;
16
+ projectId?: string | null;
17
+ title: string | null;
18
+ body: string;
18
19
  commandLabel: string;
19
20
  }
20
21
 
@@ -32,6 +33,8 @@ interface HandleTaskEditorPromptInputOptions {
32
33
  dismissOnOutsideClick: (input: Buffer, dismiss: () => void) => boolean;
33
34
  }
34
35
 
36
+ const MOUSE_EVENT_PREFIX = Buffer.from('\u001b[<', 'utf8');
37
+
35
38
  export function handleTaskEditorPromptInput(
36
39
  options: HandleTaskEditorPromptInputOptions,
37
40
  ): HandleTaskEditorPromptInputResult {
@@ -57,7 +60,9 @@ export function handleTaskEditorPromptInput(
57
60
  }
58
61
 
59
62
  let dismissed = false;
63
+ const maybeMouseSequence = input.includes(MOUSE_EVENT_PREFIX);
60
64
  if (
65
+ maybeMouseSequence &&
61
66
  dismissOnOutsideClick(input, () => {
62
67
  dismissed = true;
63
68
  })
@@ -77,13 +82,13 @@ export function handleTaskEditorPromptInput(
77
82
 
78
83
  const reduced = reduceTaskEditorPromptInput(prompt, input);
79
84
  const nextTitle = reduced.title;
80
- const nextDescription = reduced.description;
85
+ const nextBody = reduced.body;
81
86
  const nextFieldIndex = reduced.fieldIndex;
82
87
  const nextRepositoryIndex = reduced.repositoryIndex;
83
88
  const submit = reduced.submit;
84
89
  const changed =
85
90
  nextTitle !== prompt.title ||
86
- nextDescription !== prompt.description ||
91
+ nextBody !== prompt.body ||
87
92
  nextFieldIndex !== prompt.fieldIndex ||
88
93
  nextRepositoryIndex !== prompt.repositoryIndex;
89
94
 
@@ -91,7 +96,7 @@ export function handleTaskEditorPromptInput(
91
96
  ? {
92
97
  ...prompt,
93
98
  title: nextTitle,
94
- description: nextDescription,
99
+ body: nextBody,
95
100
  fieldIndex: nextFieldIndex,
96
101
  repositoryIndex: nextRepositoryIndex,
97
102
  error: null,
@@ -113,13 +118,12 @@ export function handleTaskEditorPromptInput(
113
118
  }
114
119
 
115
120
  const repositoryId = prompt.repositoryIds[nextRepositoryIndex] ?? null;
116
- const title = nextTitle.trim();
117
- if (title.length === 0) {
121
+ if (nextBody.trim().length === 0) {
118
122
  return {
119
123
  handled: true,
120
124
  nextPrompt: {
121
125
  ...changedPrompt,
122
- error: 'title required',
126
+ error: 'task body required',
123
127
  },
124
128
  markDirty: true,
125
129
  };
@@ -148,8 +152,9 @@ export function handleTaskEditorPromptInput(
148
152
  mode: prompt.mode,
149
153
  taskId: prompt.taskId,
150
154
  repositoryId,
151
- title,
152
- description: nextDescription,
155
+ projectId: null,
156
+ title: nextTitle.trim().length === 0 ? null : nextTitle.trim(),
157
+ body: nextBody,
153
158
  commandLabel: prompt.mode === 'create' ? 'tasks-create' : 'tasks-edit',
154
159
  },
155
160
  };
@@ -45,9 +45,10 @@ export function handleSeparatorPointerPress(options: HandleSeparatorPointerPress
45
45
  interface HandleMainPaneWheelInputOptions {
46
46
  target: string;
47
47
  wheelDelta: number | null;
48
- mainPaneMode: 'conversation' | 'project' | 'home';
48
+ mainPaneMode: 'conversation' | 'project' | 'home' | 'nim';
49
49
  onProjectWheel: (delta: number) => void;
50
50
  onHomeWheel: (delta: number) => void;
51
+ onNimWheel: (delta: number) => void;
51
52
  onConversationWheel: (delta: number) => void;
52
53
  markDirty: () => void;
53
54
  }
@@ -60,6 +61,8 @@ export function handleMainPaneWheelInput(options: HandleMainPaneWheelInputOption
60
61
  options.onProjectWheel(options.wheelDelta);
61
62
  } else if (options.mainPaneMode === 'home') {
62
63
  options.onHomeWheel(options.wheelDelta);
64
+ } else if (options.mainPaneMode === 'nim') {
65
+ options.onNimWheel(options.wheelDelta);
63
66
  } else {
64
67
  options.onConversationWheel(options.wheelDelta);
65
68
  }
@@ -77,7 +80,7 @@ interface HomePaneDragState {
77
80
 
78
81
  interface HandleHomePaneDragMoveOptions {
79
82
  homePaneDragState: HomePaneDragState | null;
80
- mainPaneMode: 'conversation' | 'project' | 'home';
83
+ mainPaneMode: 'conversation' | 'project' | 'home' | 'nim';
81
84
  target: string;
82
85
  isSelectionDrag: boolean;
83
86
  hasAltModifier: boolean;
@@ -14,6 +14,7 @@ interface HandleProjectPaneActionClickOptions<TSnapshot extends { directoryId: s
14
14
  ) => string | null;
15
15
  openNewThreadPrompt: (directoryId: string) => void;
16
16
  queueCloseDirectory: (directoryId: string) => void;
17
+ handleProjectPaneAction?: (action: string, directoryId: string) => boolean;
17
18
  markDirty: () => void;
18
19
  }
19
20
 
@@ -40,5 +41,12 @@ export function handleProjectPaneActionClick<TSnapshot extends { directoryId: st
40
41
  options.markDirty();
41
42
  return true;
42
43
  }
44
+ if (
45
+ action !== null &&
46
+ options.handleProjectPaneAction?.(action, options.snapshot.directoryId) === true
47
+ ) {
48
+ options.markDirty();
49
+ return true;
50
+ }
43
51
  return false;
44
52
  }
@@ -1,15 +1,13 @@
1
1
  import { basename } from 'node:path';
2
2
  import type { ConversationRailSessionSummary } from '../conversation-rail.ts';
3
- import { firstShortcutText } from '../input-shortcuts.ts';
4
- import type { resolveMuxShortcutBindings } from '../input-shortcuts.ts';
5
3
  import { buildWorkspaceRailViewRows } from '../workspace-rail-model.ts';
6
4
  import { renderWorkspaceRailAnsiRows } from '../workspace-rail.ts';
5
+ import type { ProjectPaneGitHubReviewSummary } from '../project-pane-github-review.ts';
7
6
  import type {
8
7
  StreamSessionController,
9
8
  StreamSessionStatusModel,
10
9
  } from '../../control-plane/stream-protocol.ts';
11
10
 
12
- type ResolvedMuxShortcutBindings = ReturnType<typeof resolveMuxShortcutBindings>;
13
11
  type WorkspaceRailModel = Parameters<typeof renderWorkspaceRailAnsiRows>[0];
14
12
 
15
13
  interface GitSummary {
@@ -71,12 +69,20 @@ interface BuildRailModelArgs {
71
69
  readonly projectSelectionEnabled: boolean;
72
70
  readonly repositorySelectionEnabled: boolean;
73
71
  readonly homeSelectionEnabled: boolean;
72
+ readonly nimSelectionEnabled?: boolean;
73
+ readonly tasksSelectionEnabled?: boolean;
74
+ readonly showNimEntry?: boolean;
75
+ readonly showTasksEntry?: boolean;
76
+ readonly showGitHubIntegration?: boolean;
77
+ readonly visibleGitHubDirectoryIds?: ReadonlySet<string>;
78
+ readonly expandedGitHubDirectoryIds?: ReadonlySet<string>;
79
+ readonly githubReviewByDirectoryId?: ReadonlyMap<string, ProjectPaneGitHubReviewSummary>;
80
+ readonly githubSelectionEnabled?: boolean;
81
+ readonly activeGitHubProjectId?: string | null;
74
82
  readonly repositoriesCollapsed: boolean;
75
83
  readonly collapsedRepositoryGroupIds: ReadonlySet<string>;
76
- readonly shortcutsCollapsed: boolean;
77
84
  readonly gitSummaryByDirectoryId: ReadonlyMap<string, GitSummary>;
78
85
  readonly processUsageBySessionId: ReadonlyMap<string, MuxRailProcessUsageSample>;
79
- readonly shortcutBindings: ResolvedMuxShortcutBindings;
80
86
  readonly loadingGitSummary: GitSummary;
81
87
  }
82
88
 
@@ -87,29 +93,6 @@ interface BuildRailRowsArgs extends BuildRailModelArgs {
87
93
  };
88
94
  }
89
95
 
90
- function shortcutHintText(bindings: ResolvedMuxShortcutBindings): string {
91
- const newConversation = firstShortcutText(bindings, 'mux.conversation.new') || 'ctrl+t';
92
- const critiqueConversation =
93
- firstShortcutText(bindings, 'mux.conversation.critique.open-or-create') || 'ctrl+g';
94
- const deleteConversation = firstShortcutText(bindings, 'mux.conversation.delete') || 'ctrl+x';
95
- const takeoverConversation = firstShortcutText(bindings, 'mux.conversation.takeover') || 'ctrl+l';
96
- const addProject = firstShortcutText(bindings, 'mux.directory.add') || 'ctrl+o';
97
- const closeProject = firstShortcutText(bindings, 'mux.directory.close') || 'ctrl+w';
98
- const next = firstShortcutText(bindings, 'mux.conversation.next') || 'ctrl+j';
99
- const previous = firstShortcutText(bindings, 'mux.conversation.previous') || 'ctrl+k';
100
- const interruptConversation = firstShortcutText(bindings, 'mux.conversation.interrupt');
101
- const quit = firstShortcutText(bindings, 'mux.app.interrupt-all') || 'ctrl+c';
102
- const commandMenu = firstShortcutText(bindings, 'mux.command-menu.toggle') || 'ctrl+p';
103
- const profile = firstShortcutText(bindings, 'mux.gateway.profile.toggle') || 'ctrl+shift+p';
104
- const statusTimeline =
105
- firstShortcutText(bindings, 'mux.gateway.status-timeline.toggle') || 'alt+r';
106
- const renderTrace = firstShortcutText(bindings, 'mux.gateway.render-trace.toggle') || 'ctrl+]';
107
- const switchHint = next === previous ? next : `${next}/${previous}`;
108
- const interruptHint =
109
- interruptConversation.length === 0 ? '' : ` ${interruptConversation} interrupt`;
110
- return `${commandMenu} menu ${newConversation} new ${critiqueConversation} critique ${deleteConversation} archive ${takeoverConversation} takeover ${addProject}/${closeProject} projects ${switchHint} switch nav ${profile} profile ${statusTimeline} status ${renderTrace} render${interruptHint} ←/→ collapse/expand ${quit} quit`;
111
- }
112
-
113
96
  function conversationSummary(
114
97
  conversation: MuxRailConversationRecord,
115
98
  ): ConversationRailSessionSummary {
@@ -219,14 +202,34 @@ export function buildRailModel(args: BuildRailModelArgs): WorkspaceRailModel {
219
202
  activeRepositoryId: args.activeRepositoryId,
220
203
  activeConversationId: args.activeConversationId,
221
204
  showTaskPlanningUi: true,
205
+ showNimEntry: args.showNimEntry ?? true,
206
+ showTasksEntry: args.showTasksEntry ?? true,
207
+ showGitHubIntegration: args.showGitHubIntegration ?? false,
208
+ ...(args.visibleGitHubDirectoryIds === undefined
209
+ ? {}
210
+ : {
211
+ visibleGitHubDirectoryKeys: [...args.visibleGitHubDirectoryIds],
212
+ }),
213
+ ...(args.expandedGitHubDirectoryIds === undefined
214
+ ? {}
215
+ : {
216
+ expandedGitHubDirectoryKeys: [...args.expandedGitHubDirectoryIds],
217
+ }),
218
+ ...(args.githubReviewByDirectoryId === undefined
219
+ ? {}
220
+ : {
221
+ githubReviewByDirectoryKey: args.githubReviewByDirectoryId,
222
+ }),
222
223
  projectSelectionEnabled: args.projectSelectionEnabled,
224
+ githubSelectionEnabled: args.githubSelectionEnabled ?? false,
225
+ activeGitHubProjectId: args.activeGitHubProjectId ?? null,
223
226
  repositorySelectionEnabled: args.repositorySelectionEnabled,
224
227
  homeSelectionEnabled: args.homeSelectionEnabled,
228
+ nimSelectionEnabled: args.nimSelectionEnabled ?? false,
229
+ tasksSelectionEnabled: args.tasksSelectionEnabled ?? false,
225
230
  repositoriesCollapsed: args.repositoriesCollapsed,
226
231
  collapsedRepositoryGroupIds: [...args.collapsedRepositoryGroupIds],
227
232
  processes: [],
228
- shortcutHint: shortcutHintText(args.shortcutBindings),
229
- shortcutsCollapsed: args.shortcutsCollapsed,
230
233
  nowMs: Date.now(),
231
234
  };
232
235
  }