@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,118 +0,0 @@
1
- interface HandleLeftRailActionClickOptions {
2
- action: string | null;
3
- selectedProjectId: string | null;
4
- selectedRepositoryId: string | null;
5
- activeConversationId: string | null;
6
- repositoriesCollapsed: boolean;
7
- clearConversationTitleEditClickState: () => void;
8
- resolveDirectoryForAction: () => string | null;
9
- openNewThreadPrompt: (directoryId: string) => void;
10
- queueArchiveConversation: (conversationId: string) => void;
11
- openAddDirectoryPrompt: () => void;
12
- openRepositoryPromptForCreate: () => void;
13
- repositoryExists: (repositoryId: string) => boolean;
14
- openRepositoryPromptForEdit: (repositoryId: string) => void;
15
- queueArchiveRepository: (repositoryId: string) => void;
16
- toggleRepositoryGroup: (repositoryId: string) => void;
17
- selectLeftNavRepository: (repositoryId: string) => void;
18
- expandAllRepositoryGroups: () => void;
19
- collapseAllRepositoryGroups: () => void;
20
- enterHomePane: () => void;
21
- queueCloseDirectory: (directoryId: string) => void;
22
- toggleShortcutsCollapsed: () => void;
23
- markDirty: () => void;
24
- }
25
-
26
- export function handleLeftRailActionClick(options: HandleLeftRailActionClickOptions): boolean {
27
- if (options.action === 'conversation.new') {
28
- options.clearConversationTitleEditClickState();
29
- const targetDirectoryId = options.selectedProjectId ?? options.resolveDirectoryForAction();
30
- if (targetDirectoryId !== null) {
31
- options.openNewThreadPrompt(targetDirectoryId);
32
- }
33
- options.markDirty();
34
- return true;
35
- }
36
- if (options.action === 'conversation.delete') {
37
- options.clearConversationTitleEditClickState();
38
- if (options.activeConversationId !== null) {
39
- options.queueArchiveConversation(options.activeConversationId);
40
- }
41
- options.markDirty();
42
- return true;
43
- }
44
- if (options.action === 'project.add') {
45
- options.clearConversationTitleEditClickState();
46
- options.openAddDirectoryPrompt();
47
- options.markDirty();
48
- return true;
49
- }
50
- if (options.action === 'repository.add') {
51
- options.clearConversationTitleEditClickState();
52
- options.openRepositoryPromptForCreate();
53
- return true;
54
- }
55
- if (options.action === 'repository.edit') {
56
- options.clearConversationTitleEditClickState();
57
- if (
58
- options.selectedRepositoryId !== null &&
59
- options.repositoryExists(options.selectedRepositoryId)
60
- ) {
61
- options.openRepositoryPromptForEdit(options.selectedRepositoryId);
62
- }
63
- options.markDirty();
64
- return true;
65
- }
66
- if (options.action === 'repository.archive') {
67
- options.clearConversationTitleEditClickState();
68
- if (
69
- options.selectedRepositoryId !== null &&
70
- options.repositoryExists(options.selectedRepositoryId)
71
- ) {
72
- options.queueArchiveRepository(options.selectedRepositoryId);
73
- }
74
- options.markDirty();
75
- return true;
76
- }
77
- if (options.action === 'repository.toggle') {
78
- options.clearConversationTitleEditClickState();
79
- if (options.selectedRepositoryId !== null) {
80
- options.toggleRepositoryGroup(options.selectedRepositoryId);
81
- options.selectLeftNavRepository(options.selectedRepositoryId);
82
- }
83
- options.markDirty();
84
- return true;
85
- }
86
- if (options.action === 'repositories.toggle') {
87
- options.clearConversationTitleEditClickState();
88
- if (options.repositoriesCollapsed) {
89
- options.expandAllRepositoryGroups();
90
- } else {
91
- options.collapseAllRepositoryGroups();
92
- }
93
- options.markDirty();
94
- return true;
95
- }
96
- if (options.action === 'home.open') {
97
- options.clearConversationTitleEditClickState();
98
- options.enterHomePane();
99
- options.markDirty();
100
- return true;
101
- }
102
- if (options.action === 'project.close') {
103
- options.clearConversationTitleEditClickState();
104
- const targetDirectoryId = options.selectedProjectId ?? options.resolveDirectoryForAction();
105
- if (targetDirectoryId !== null) {
106
- options.queueCloseDirectory(targetDirectoryId);
107
- }
108
- options.markDirty();
109
- return true;
110
- }
111
- if (options.action === 'shortcuts.toggle') {
112
- options.clearConversationTitleEditClickState();
113
- options.toggleShortcutsCollapsed();
114
- options.markDirty();
115
- return true;
116
- }
117
- return false;
118
- }
@@ -1,82 +0,0 @@
1
- import { detectConversationDoubleClick } from '../double-click.ts';
2
-
3
- interface ConversationTitleEditClickState {
4
- readonly conversationId: string;
5
- readonly atMs: number;
6
- }
7
-
8
- interface HandleLeftRailConversationClickOptions {
9
- selectedConversationId: string | null;
10
- selectedProjectId: string | null;
11
- supportsConversationTitleEditClick: boolean;
12
- previousClickState: ConversationTitleEditClickState | null;
13
- nowMs: number;
14
- conversationTitleEditDoubleClickWindowMs: number;
15
- activeConversationId: string | null;
16
- isConversationPaneActive: boolean;
17
- setConversationClickState: (next: ConversationTitleEditClickState | null) => void;
18
- ensureConversationPaneActive: (conversationId: string) => void;
19
- beginConversationTitleEdit: (conversationId: string) => void;
20
- queueActivateConversation: (conversationId: string) => void;
21
- queueActivateConversationAndEdit: (conversationId: string) => void;
22
- directoriesHas: (directoryId: string) => boolean;
23
- enterProjectPane: (directoryId: string) => void;
24
- markDirty: () => void;
25
- }
26
-
27
- export function handleLeftRailConversationClick(
28
- options: HandleLeftRailConversationClickOptions,
29
- ): boolean {
30
- const conversationClick =
31
- options.selectedConversationId !== null && options.supportsConversationTitleEditClick
32
- ? detectConversationDoubleClick(
33
- options.previousClickState,
34
- options.selectedConversationId,
35
- options.nowMs,
36
- options.conversationTitleEditDoubleClickWindowMs,
37
- )
38
- : {
39
- doubleClick: false,
40
- nextState: null,
41
- };
42
- options.setConversationClickState(conversationClick.nextState);
43
-
44
- if (
45
- options.selectedConversationId !== null &&
46
- options.selectedConversationId === options.activeConversationId
47
- ) {
48
- if (!options.isConversationPaneActive) {
49
- options.ensureConversationPaneActive(options.selectedConversationId);
50
- }
51
- if (conversationClick.doubleClick) {
52
- options.beginConversationTitleEdit(options.selectedConversationId);
53
- }
54
- options.markDirty();
55
- return true;
56
- }
57
-
58
- if (options.selectedConversationId !== null) {
59
- if (conversationClick.doubleClick) {
60
- options.queueActivateConversationAndEdit(options.selectedConversationId);
61
- } else {
62
- options.queueActivateConversation(options.selectedConversationId);
63
- }
64
- options.markDirty();
65
- return true;
66
- }
67
-
68
- if (
69
- options.selectedConversationId === null &&
70
- options.selectedProjectId !== null &&
71
- options.directoriesHas(options.selectedProjectId)
72
- ) {
73
- options.setConversationClickState(null);
74
- options.enterProjectPane(options.selectedProjectId);
75
- options.markDirty();
76
- return true;
77
- }
78
-
79
- options.setConversationClickState(null);
80
- options.markDirty();
81
- return true;
82
- }
@@ -1,74 +0,0 @@
1
- import {
2
- actionAtWorkspaceRailCell,
3
- conversationIdAtWorkspaceRailRow,
4
- kindAtWorkspaceRailRow,
5
- projectIdAtWorkspaceRailRow,
6
- repositoryIdAtWorkspaceRailRow,
7
- } from '../workspace-rail-model.ts';
8
- import type { buildWorkspaceRailViewRows } from '../workspace-rail-model.ts';
9
-
10
- export interface LeftRailPointerContext {
11
- readonly selectedConversationId: string | null;
12
- readonly selectedProjectId: string | null;
13
- readonly selectedRepositoryId: string | null;
14
- readonly selectedAction: string | null;
15
- readonly supportsConversationTitleEditClick: boolean;
16
- }
17
-
18
- interface HandleLeftRailPointerClickOptions {
19
- clickEligible: boolean;
20
- rows: ReturnType<typeof buildWorkspaceRailViewRows>;
21
- paneRows: number;
22
- leftCols: number;
23
- pointerRow: number;
24
- pointerCol: number;
25
- hasConversationTitleEdit: boolean;
26
- conversationTitleEditConversationId: string | null;
27
- stopConversationTitleEdit: () => void;
28
- hasSelection: boolean;
29
- clearSelection: () => void;
30
- handleAction: (context: LeftRailPointerContext) => boolean;
31
- handleConversation: (context: LeftRailPointerContext) => void;
32
- }
33
-
34
- export function handleLeftRailPointerClick(options: HandleLeftRailPointerClickOptions): boolean {
35
- if (!options.clickEligible) {
36
- return false;
37
- }
38
- const rowIndex = Math.max(0, Math.min(options.paneRows - 1, options.pointerRow - 1));
39
- const colIndex = Math.max(0, Math.min(options.leftCols - 1, options.pointerCol - 1));
40
- const selectedConversationId = conversationIdAtWorkspaceRailRow(options.rows, rowIndex);
41
- const selectedProjectId = projectIdAtWorkspaceRailRow(options.rows, rowIndex);
42
- const selectedRepositoryId = repositoryIdAtWorkspaceRailRow(options.rows, rowIndex);
43
- const selectedAction = actionAtWorkspaceRailCell(
44
- options.rows,
45
- rowIndex,
46
- colIndex,
47
- options.leftCols,
48
- );
49
- const selectedRowKind = kindAtWorkspaceRailRow(options.rows, rowIndex);
50
- const supportsConversationTitleEditClick =
51
- selectedRowKind === 'conversation-title' || selectedRowKind === 'conversation-body';
52
- const keepTitleEditActive =
53
- options.hasConversationTitleEdit &&
54
- selectedConversationId === options.conversationTitleEditConversationId &&
55
- supportsConversationTitleEditClick;
56
- if (!keepTitleEditActive && options.hasConversationTitleEdit) {
57
- options.stopConversationTitleEdit();
58
- }
59
- if (options.hasSelection) {
60
- options.clearSelection();
61
- }
62
- const context: LeftRailPointerContext = {
63
- selectedConversationId,
64
- selectedProjectId,
65
- selectedRepositoryId,
66
- selectedAction,
67
- supportsConversationTitleEditClick,
68
- };
69
- if (options.handleAction(context)) {
70
- return true;
71
- }
72
- options.handleConversation(context);
73
- return true;
74
- }
@@ -1,206 +0,0 @@
1
- import {
2
- insertTaskComposerText,
3
- taskComposerBackspace,
4
- taskComposerDeleteForward,
5
- taskComposerDeleteToLineEnd,
6
- taskComposerDeleteToLineStart,
7
- taskComposerDeleteWordLeft,
8
- taskComposerMoveLeft,
9
- taskComposerMoveLineEnd,
10
- taskComposerMoveLineStart,
11
- taskComposerMoveRight,
12
- taskComposerMoveVertical,
13
- taskComposerMoveWordLeft,
14
- taskComposerMoveWordRight,
15
- type TaskComposerBuffer,
16
- } from '../task-composer.ts';
17
- import { detectTaskScreenKeybindingAction } from '../task-screen-keybindings.ts';
18
-
19
- type TaskPaneActionShortcut =
20
- | 'task.ready'
21
- | 'task.draft'
22
- | 'task.complete'
23
- | 'task.reorder-up'
24
- | 'task.reorder-down';
25
-
26
- interface TaskEditorTargetDraft {
27
- kind: 'draft';
28
- }
29
-
30
- interface TaskEditorTargetTask {
31
- kind: 'task';
32
- taskId: string;
33
- }
34
-
35
- interface HandleTaskPaneShortcutInputOptions {
36
- input: Buffer;
37
- mainPaneMode: 'conversation' | 'project' | 'home';
38
- taskScreenKeybindings: Parameters<typeof detectTaskScreenKeybindingAction>[1];
39
- taskEditorTarget: TaskEditorTargetDraft | TaskEditorTargetTask;
40
- homeEditorBuffer: () => TaskComposerBuffer;
41
- updateHomeEditorBuffer: (next: TaskComposerBuffer) => void;
42
- moveTaskEditorFocusUp: () => void;
43
- focusDraftComposer: () => void;
44
- submitDraftTaskFromComposer: () => void;
45
- runTaskPaneAction: (action: TaskPaneActionShortcut) => void;
46
- selectRepositoryByDirection: (direction: 1 | -1) => void;
47
- getTaskRepositoryDropdownOpen: () => boolean;
48
- setTaskRepositoryDropdownOpen: (open: boolean) => void;
49
- markDirty: () => void;
50
- }
51
-
52
- export function handleTaskPaneShortcutInput(options: HandleTaskPaneShortcutInputOptions): boolean {
53
- const {
54
- input,
55
- mainPaneMode,
56
- taskScreenKeybindings,
57
- taskEditorTarget,
58
- homeEditorBuffer,
59
- updateHomeEditorBuffer,
60
- moveTaskEditorFocusUp,
61
- focusDraftComposer,
62
- submitDraftTaskFromComposer,
63
- runTaskPaneAction,
64
- selectRepositoryByDirection,
65
- getTaskRepositoryDropdownOpen,
66
- setTaskRepositoryDropdownOpen,
67
- markDirty,
68
- } = options;
69
- if (mainPaneMode !== 'home') {
70
- return false;
71
- }
72
- const action = detectTaskScreenKeybindingAction(input, taskScreenKeybindings);
73
- if (action !== null) {
74
- if (action === 'mux.home.repo.dropdown.toggle') {
75
- setTaskRepositoryDropdownOpen(!getTaskRepositoryDropdownOpen());
76
- markDirty();
77
- return true;
78
- }
79
- if (action === 'mux.home.repo.next') {
80
- setTaskRepositoryDropdownOpen(true);
81
- selectRepositoryByDirection(1);
82
- return true;
83
- }
84
- if (action === 'mux.home.repo.previous') {
85
- setTaskRepositoryDropdownOpen(true);
86
- selectRepositoryByDirection(-1);
87
- return true;
88
- }
89
- if (action === 'mux.home.task.status.ready') {
90
- runTaskPaneAction('task.ready');
91
- return true;
92
- }
93
- if (action === 'mux.home.task.status.draft') {
94
- runTaskPaneAction('task.draft');
95
- return true;
96
- }
97
- if (action === 'mux.home.task.status.complete') {
98
- runTaskPaneAction('task.complete');
99
- return true;
100
- }
101
- if (action === 'mux.home.task.reorder.up') {
102
- runTaskPaneAction('task.reorder-up');
103
- return true;
104
- }
105
- if (action === 'mux.home.task.reorder.down') {
106
- runTaskPaneAction('task.reorder-down');
107
- return true;
108
- }
109
- if (action === 'mux.home.task.newline') {
110
- updateHomeEditorBuffer(insertTaskComposerText(homeEditorBuffer(), '\n'));
111
- return true;
112
- }
113
- if (action === 'mux.home.task.submit') {
114
- if (taskEditorTarget.kind === 'draft') {
115
- submitDraftTaskFromComposer();
116
- } else {
117
- focusDraftComposer();
118
- }
119
- return true;
120
- }
121
- if (action === 'mux.home.editor.cursor.left') {
122
- updateHomeEditorBuffer(taskComposerMoveLeft(homeEditorBuffer()));
123
- return true;
124
- }
125
- if (action === 'mux.home.editor.cursor.right') {
126
- updateHomeEditorBuffer(taskComposerMoveRight(homeEditorBuffer()));
127
- return true;
128
- }
129
- if (action === 'mux.home.editor.cursor.up') {
130
- const vertical = taskComposerMoveVertical(homeEditorBuffer(), -1);
131
- if (vertical.hitBoundary) {
132
- moveTaskEditorFocusUp();
133
- } else {
134
- updateHomeEditorBuffer(vertical.next);
135
- }
136
- return true;
137
- }
138
- if (action === 'mux.home.editor.cursor.down') {
139
- if (taskEditorTarget.kind === 'task') {
140
- const vertical = taskComposerMoveVertical(homeEditorBuffer(), 1);
141
- if (vertical.hitBoundary) {
142
- focusDraftComposer();
143
- } else {
144
- updateHomeEditorBuffer(vertical.next);
145
- }
146
- } else {
147
- updateHomeEditorBuffer(taskComposerMoveVertical(homeEditorBuffer(), 1).next);
148
- }
149
- return true;
150
- }
151
- if (action === 'mux.home.editor.line.start') {
152
- updateHomeEditorBuffer(taskComposerMoveLineStart(homeEditorBuffer()));
153
- return true;
154
- }
155
- if (action === 'mux.home.editor.line.end') {
156
- updateHomeEditorBuffer(taskComposerMoveLineEnd(homeEditorBuffer()));
157
- return true;
158
- }
159
- if (action === 'mux.home.editor.word.left') {
160
- updateHomeEditorBuffer(taskComposerMoveWordLeft(homeEditorBuffer()));
161
- return true;
162
- }
163
- if (action === 'mux.home.editor.word.right') {
164
- updateHomeEditorBuffer(taskComposerMoveWordRight(homeEditorBuffer()));
165
- return true;
166
- }
167
- if (action === 'mux.home.editor.delete.backward') {
168
- updateHomeEditorBuffer(taskComposerBackspace(homeEditorBuffer()));
169
- return true;
170
- }
171
- if (action === 'mux.home.editor.delete.forward') {
172
- updateHomeEditorBuffer(taskComposerDeleteForward(homeEditorBuffer()));
173
- return true;
174
- }
175
- if (action === 'mux.home.editor.delete.word.backward') {
176
- updateHomeEditorBuffer(taskComposerDeleteWordLeft(homeEditorBuffer()));
177
- return true;
178
- }
179
- if (action === 'mux.home.editor.delete.line.start') {
180
- updateHomeEditorBuffer(taskComposerDeleteToLineStart(homeEditorBuffer()));
181
- return true;
182
- }
183
- if (action === 'mux.home.editor.delete.line.end') {
184
- updateHomeEditorBuffer(taskComposerDeleteToLineEnd(homeEditorBuffer()));
185
- return true;
186
- }
187
- }
188
-
189
- if (input.includes(0x1b)) {
190
- return false;
191
- }
192
-
193
- let next = homeEditorBuffer();
194
- let changed = false;
195
- for (const byte of input) {
196
- if (byte >= 32 && byte <= 126) {
197
- next = insertTaskComposerText(next, String.fromCharCode(byte));
198
- changed = true;
199
- }
200
- }
201
- if (!changed) {
202
- return false;
203
- }
204
- updateHomeEditorBuffer(next);
205
- return true;
206
- }
@@ -1,164 +0,0 @@
1
- import {
2
- addDirectoryByPath as addDirectoryByPathFn,
3
- archiveConversation as archiveConversationFn,
4
- closeDirectory as closeDirectoryFn,
5
- } from '../mux/live-mux/actions-conversation.ts';
6
-
7
- interface RuntimeConversationStateLike {
8
- readonly directoryId: string | null;
9
- readonly live: boolean;
10
- }
11
-
12
- interface RuntimeDirectoryRecordLike {
13
- readonly directoryId: string;
14
- }
15
-
16
- interface RuntimeDirectoryActionService<TDirectoryRecord extends RuntimeDirectoryRecordLike> {
17
- closePtySession(sessionId: string): Promise<unknown>;
18
- removeSession(sessionId: string): Promise<unknown>;
19
- archiveConversation(sessionId: string): Promise<unknown>;
20
- upsertDirectory(input: { directoryId: string; path: string }): Promise<TDirectoryRecord | null>;
21
- archiveDirectory(directoryId: string): Promise<unknown>;
22
- }
23
-
24
- interface RuntimeDirectoryActionsOptions<
25
- TDirectoryRecord extends RuntimeDirectoryRecordLike,
26
- TConversationState extends RuntimeConversationStateLike,
27
- > {
28
- readonly controlPlaneService: RuntimeDirectoryActionService<TDirectoryRecord>;
29
- readonly conversations: () => ReadonlyMap<string, TConversationState>;
30
- readonly orderedConversationIds: () => readonly string[];
31
- readonly conversationDirectoryId: (sessionId: string) => string | null;
32
- readonly conversationLive: (sessionId: string) => boolean;
33
- readonly removeConversationState: (sessionId: string) => void;
34
- readonly unsubscribeConversationEvents: (sessionId: string) => Promise<void>;
35
- readonly activeConversationId: () => string | null;
36
- readonly setActiveConversationId: (sessionId: string | null) => void;
37
- readonly activateConversation: (sessionId: string) => Promise<unknown>;
38
- readonly resolveActiveDirectoryId: () => string | null;
39
- readonly enterProjectPane: (directoryId: string) => void;
40
- readonly markDirty: () => void;
41
- readonly isSessionNotFoundError: (error: unknown) => boolean;
42
- readonly isConversationNotFoundError: (error: unknown) => boolean;
43
- readonly createDirectoryId: () => string;
44
- readonly resolveWorkspacePathForMux: (rawPath: string) => string;
45
- readonly setDirectory: (directory: TDirectoryRecord) => void;
46
- readonly directoryIdOf: (directory: TDirectoryRecord) => string;
47
- readonly setActiveDirectoryId: (directoryId: string | null) => void;
48
- readonly syncGitStateWithDirectories: () => void;
49
- readonly noteGitActivity: (directoryId: string) => void;
50
- readonly hydratePersistedConversationsForDirectory: (directoryId: string) => Promise<unknown>;
51
- readonly findConversationIdByDirectory: (directoryId: string) => string | null;
52
- readonly directoriesHas: (directoryId: string) => boolean;
53
- readonly deleteDirectory: (directoryId: string) => void;
54
- readonly deleteDirectoryGitState: (directoryId: string) => void;
55
- readonly projectPaneSnapshotDirectoryId: () => string | null;
56
- readonly clearProjectPaneSnapshot: () => void;
57
- readonly directoriesSize: () => number;
58
- readonly invocationDirectory: string;
59
- readonly activeDirectoryId: () => string | null;
60
- readonly firstDirectoryId: () => string | null;
61
- }
62
-
63
- export class RuntimeDirectoryActions<
64
- TDirectoryRecord extends RuntimeDirectoryRecordLike,
65
- TConversationState extends RuntimeConversationStateLike,
66
- > {
67
- constructor(
68
- private readonly options: RuntimeDirectoryActionsOptions<TDirectoryRecord, TConversationState>,
69
- ) {}
70
-
71
- async archiveConversation(sessionId: string): Promise<void> {
72
- await archiveConversationFn({
73
- sessionId,
74
- conversations: this.options.conversations(),
75
- closePtySession: async (targetSessionId) => {
76
- await this.options.controlPlaneService.closePtySession(targetSessionId);
77
- },
78
- removeSession: async (targetSessionId) => {
79
- await this.options.controlPlaneService.removeSession(targetSessionId);
80
- },
81
- isSessionNotFoundError: this.options.isSessionNotFoundError,
82
- archiveConversationRecord: async (targetSessionId) => {
83
- await this.options.controlPlaneService.archiveConversation(targetSessionId);
84
- },
85
- isConversationNotFoundError: this.options.isConversationNotFoundError,
86
- unsubscribeConversationEvents: this.options.unsubscribeConversationEvents,
87
- removeConversationState: this.options.removeConversationState,
88
- activeConversationId: this.options.activeConversationId(),
89
- setActiveConversationId: this.options.setActiveConversationId,
90
- orderedConversationIds: this.options.orderedConversationIds,
91
- conversationDirectoryId: this.options.conversationDirectoryId,
92
- resolveActiveDirectoryId: this.options.resolveActiveDirectoryId,
93
- enterProjectPane: this.options.enterProjectPane,
94
- activateConversation: this.options.activateConversation,
95
- markDirty: this.options.markDirty,
96
- });
97
- }
98
-
99
- async addDirectoryByPath(rawPath: string): Promise<void> {
100
- await addDirectoryByPathFn({
101
- rawPath,
102
- resolveWorkspacePathForMux: this.options.resolveWorkspacePathForMux,
103
- upsertDirectory: async (path) => {
104
- return await this.options.controlPlaneService.upsertDirectory({
105
- directoryId: this.options.createDirectoryId(),
106
- path,
107
- });
108
- },
109
- setDirectory: this.options.setDirectory,
110
- directoryIdOf: this.options.directoryIdOf,
111
- setActiveDirectoryId: (directoryId) => {
112
- this.options.setActiveDirectoryId(directoryId);
113
- },
114
- syncGitStateWithDirectories: this.options.syncGitStateWithDirectories,
115
- noteGitActivity: this.options.noteGitActivity,
116
- hydratePersistedConversationsForDirectory:
117
- this.options.hydratePersistedConversationsForDirectory,
118
- findConversationIdByDirectory: this.options.findConversationIdByDirectory,
119
- activateConversation: this.options.activateConversation,
120
- enterProjectPane: this.options.enterProjectPane,
121
- markDirty: this.options.markDirty,
122
- });
123
- }
124
-
125
- async closeDirectory(directoryId: string): Promise<void> {
126
- await closeDirectoryFn({
127
- directoryId,
128
- directoriesHas: this.options.directoriesHas,
129
- orderedConversationIds: this.options.orderedConversationIds,
130
- conversationDirectoryId: this.options.conversationDirectoryId,
131
- conversationLive: this.options.conversationLive,
132
- closePtySession: async (sessionId) => {
133
- await this.options.controlPlaneService.closePtySession(sessionId);
134
- },
135
- archiveConversationRecord: async (sessionId) => {
136
- await this.options.controlPlaneService.archiveConversation(sessionId);
137
- },
138
- unsubscribeConversationEvents: this.options.unsubscribeConversationEvents,
139
- removeConversationState: this.options.removeConversationState,
140
- activeConversationId: this.options.activeConversationId(),
141
- setActiveConversationId: this.options.setActiveConversationId,
142
- archiveDirectory: async (targetDirectoryId) => {
143
- await this.options.controlPlaneService.archiveDirectory(targetDirectoryId);
144
- },
145
- deleteDirectory: this.options.deleteDirectory,
146
- deleteDirectoryGitState: this.options.deleteDirectoryGitState,
147
- projectPaneSnapshotDirectoryId: this.options.projectPaneSnapshotDirectoryId(),
148
- clearProjectPaneSnapshot: this.options.clearProjectPaneSnapshot,
149
- directoriesSize: this.options.directoriesSize,
150
- addDirectoryByPath: async (path) => {
151
- await this.addDirectoryByPath(path);
152
- },
153
- invocationDirectory: this.options.invocationDirectory,
154
- activeDirectoryId: this.options.activeDirectoryId(),
155
- setActiveDirectoryId: this.options.setActiveDirectoryId,
156
- firstDirectoryId: this.options.firstDirectoryId,
157
- noteGitActivity: this.options.noteGitActivity,
158
- resolveActiveDirectoryId: this.options.resolveActiveDirectoryId,
159
- activateConversation: this.options.activateConversation,
160
- enterProjectPane: this.options.enterProjectPane,
161
- markDirty: this.options.markDirty,
162
- });
163
- }
164
- }
@@ -1,50 +0,0 @@
1
- import { ConversationInputForwarder } from '../ui/conversation-input-forwarder.ts';
2
- import { InputPreflight } from '../ui/input-preflight.ts';
3
-
4
- type InputPreflightOptions = ConstructorParameters<typeof InputPreflight>[0];
5
- type ConversationInputForwarderOptions = ConstructorParameters<
6
- typeof ConversationInputForwarder
7
- >[0];
8
-
9
- interface RuntimeInputPipelineOptions {
10
- readonly preflight: InputPreflightOptions;
11
- readonly forwarder: ConversationInputForwarderOptions;
12
- }
13
-
14
- interface RuntimeInputPipelineDependencies {
15
- readonly createInputPreflight?: (
16
- options: InputPreflightOptions,
17
- ) => Pick<InputPreflight, 'nextInput'>;
18
- readonly createConversationInputForwarder?: (
19
- options: ConversationInputForwarderOptions,
20
- ) => Pick<ConversationInputForwarder, 'handleInput'>;
21
- }
22
-
23
- export class RuntimeInputPipeline {
24
- private readonly inputPreflight: Pick<InputPreflight, 'nextInput'>;
25
- private readonly conversationInputForwarder: Pick<ConversationInputForwarder, 'handleInput'>;
26
-
27
- constructor(
28
- options: RuntimeInputPipelineOptions,
29
- dependencies: RuntimeInputPipelineDependencies = {},
30
- ) {
31
- const createInputPreflight =
32
- dependencies.createInputPreflight ??
33
- ((preflightOptions: InputPreflightOptions) => new InputPreflight(preflightOptions));
34
- const createConversationInputForwarder =
35
- dependencies.createConversationInputForwarder ??
36
- ((forwarderOptions: ConversationInputForwarderOptions) =>
37
- new ConversationInputForwarder(forwarderOptions));
38
-
39
- this.inputPreflight = createInputPreflight(options.preflight);
40
- this.conversationInputForwarder = createConversationInputForwarder(options.forwarder);
41
- }
42
-
43
- handleInput(input: Buffer): void {
44
- const sanitized = this.inputPreflight.nextInput(input);
45
- if (sanitized === null) {
46
- return;
47
- }
48
- this.conversationInputForwarder.handleInput(sanitized);
49
- }
50
- }