@jmoyers/harness 0.1.11 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +31 -39
  2. package/package.json +31 -11
  3. package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
  4. package/packages/harness-ai/src/stream-text.ts +13 -91
  5. package/packages/harness-ui/src/frame-primitives.ts +158 -0
  6. package/packages/harness-ui/src/index.ts +18 -0
  7. package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
  8. package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
  9. package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
  10. package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
  11. package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
  12. package/packages/harness-ui/src/interaction/input.ts +420 -0
  13. package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
  14. package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
  15. package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
  16. package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
  17. package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
  18. package/packages/harness-ui/src/kit.ts +476 -0
  19. package/packages/harness-ui/src/layout.ts +238 -0
  20. package/packages/harness-ui/src/modal-manager.ts +222 -0
  21. package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
  22. package/packages/harness-ui/src/surface.ts +252 -0
  23. package/packages/harness-ui/src/text-layout.ts +210 -0
  24. package/packages/nim-core/src/contracts.ts +239 -0
  25. package/packages/nim-core/src/event-store.ts +299 -0
  26. package/packages/nim-core/src/events.ts +53 -0
  27. package/packages/nim-core/src/index.ts +9 -0
  28. package/packages/nim-core/src/provider-router.ts +129 -0
  29. package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
  30. package/packages/nim-core/src/runtime-factory.ts +49 -0
  31. package/packages/nim-core/src/runtime.ts +1797 -0
  32. package/packages/nim-core/src/session-store.ts +516 -0
  33. package/packages/nim-core/src/telemetry.ts +48 -0
  34. package/packages/nim-test-tui/src/index.ts +150 -0
  35. package/packages/nim-ui-core/src/index.ts +1 -0
  36. package/packages/nim-ui-core/src/projection.ts +87 -0
  37. package/scripts/codex-live-mux-runtime.ts +2 -3872
  38. package/scripts/control-plane-daemon.ts +11 -0
  39. package/scripts/harness-bin.js +5 -0
  40. package/scripts/harness-commands.ts +300 -0
  41. package/scripts/harness-runtime.ts +82 -0
  42. package/scripts/harness.ts +33 -3019
  43. package/scripts/nim-tui-smoke.ts +748 -0
  44. package/src/cli/auth/runtime.ts +948 -0
  45. package/src/cli/gateway/runtime.ts +1872 -0
  46. package/src/cli/parsing/flags.ts +23 -0
  47. package/src/cli/parsing/session.ts +42 -0
  48. package/src/cli/runtime/context.ts +193 -0
  49. package/src/cli/runtime-app/application.ts +392 -0
  50. package/src/cli/runtime-infra/gateway-control.ts +729 -0
  51. package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
  52. package/src/cli/workflows/runtime.ts +965 -0
  53. package/src/clients/tui/left-rail-interactions.ts +519 -0
  54. package/src/clients/tui/main-pane-interactions.ts +509 -0
  55. package/src/clients/tui/modal-input-routing.ts +71 -0
  56. package/src/clients/tui/render-snapshot-adapter.ts +88 -0
  57. package/src/clients/web/synced-selectors.ts +132 -0
  58. package/src/codex/live-session.ts +82 -29
  59. package/src/config/config-core.ts +348 -8
  60. package/src/config/harness.config.template.jsonc +33 -0
  61. package/src/control-plane/agent-realtime-api.ts +82 -427
  62. package/src/control-plane/session-summary.ts +10 -81
  63. package/src/control-plane/status/reducer-base.ts +12 -12
  64. package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
  65. package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
  66. package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
  67. package/src/control-plane/stream-client.ts +12 -2
  68. package/src/control-plane/stream-command-parser.ts +83 -143
  69. package/src/control-plane/stream-protocol.ts +53 -37
  70. package/src/control-plane/stream-server-command.ts +376 -69
  71. package/src/control-plane/stream-server-session-runtime.ts +3 -2
  72. package/src/control-plane/stream-server.ts +864 -70
  73. package/src/control-plane/stream-session-runtime-types.ts +41 -0
  74. package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
  75. package/src/core/state/observed-stream-cursor.ts +43 -0
  76. package/src/core/state/synced-observed-state.ts +273 -0
  77. package/src/core/store/harness-synced-store.ts +81 -0
  78. package/src/diff/budget.ts +136 -0
  79. package/src/diff/build.ts +289 -0
  80. package/src/diff/chunker.ts +146 -0
  81. package/src/diff/git-invoke.ts +315 -0
  82. package/src/diff/git-parse.ts +472 -0
  83. package/src/diff/hash.ts +70 -0
  84. package/src/diff/index.ts +24 -0
  85. package/src/diff/normalize.ts +134 -0
  86. package/src/diff/types.ts +178 -0
  87. package/src/diff-ui/args.ts +346 -0
  88. package/src/diff-ui/commands.ts +123 -0
  89. package/src/diff-ui/finder.ts +94 -0
  90. package/src/diff-ui/highlight.ts +127 -0
  91. package/src/diff-ui/index.ts +2 -0
  92. package/src/diff-ui/model.ts +141 -0
  93. package/src/diff-ui/pager.ts +412 -0
  94. package/src/diff-ui/render.ts +337 -0
  95. package/src/diff-ui/runtime.ts +379 -0
  96. package/src/diff-ui/state.ts +224 -0
  97. package/src/diff-ui/types.ts +236 -0
  98. package/src/domain/workspace.ts +68 -5
  99. package/src/mux/control-plane-op-queue.ts +93 -7
  100. package/src/mux/conversation-rail.ts +28 -71
  101. package/src/mux/dual-pane-core.ts +13 -13
  102. package/src/mux/harness-core-ui.ts +313 -42
  103. package/src/mux/input-shortcuts.ts +13 -131
  104. package/src/mux/keybinding-catalog.ts +340 -0
  105. package/src/mux/keybinding-registry.ts +103 -0
  106. package/src/mux/live-mux/command-menu-open-in.ts +280 -0
  107. package/src/mux/live-mux/command-menu.ts +167 -4
  108. package/src/mux/live-mux/conversation-state.ts +13 -0
  109. package/src/mux/live-mux/directory-resolution.ts +1 -1
  110. package/src/mux/live-mux/git-snapshot.ts +33 -2
  111. package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
  112. package/src/mux/live-mux/home-pane-drop.ts +1 -1
  113. package/src/mux/live-mux/home-pane-pointer.ts +10 -0
  114. package/src/mux/live-mux/input-forwarding.ts +59 -2
  115. package/src/mux/live-mux/left-nav-activation.ts +124 -7
  116. package/src/mux/live-mux/left-nav.ts +35 -0
  117. package/src/mux/live-mux/link-click.ts +292 -0
  118. package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
  119. package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
  120. package/src/mux/live-mux/modal-input-reducers.ts +77 -12
  121. package/src/mux/live-mux/modal-overlays.ts +168 -34
  122. package/src/mux/live-mux/modal-pointer.ts +3 -7
  123. package/src/mux/live-mux/modal-prompt-handlers.ts +23 -2
  124. package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
  125. package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
  126. package/src/mux/live-mux/pointer-routing.ts +5 -2
  127. package/src/mux/live-mux/project-pane-pointer.ts +8 -0
  128. package/src/mux/live-mux/rail-layout.ts +33 -30
  129. package/src/mux/live-mux/release-notes.ts +383 -0
  130. package/src/mux/live-mux/render-trace-analysis.ts +52 -7
  131. package/src/mux/live-mux/repository-folding.ts +3 -0
  132. package/src/mux/live-mux/selection.ts +0 -4
  133. package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
  134. package/src/mux/project-pane-github-review.ts +271 -0
  135. package/src/mux/render-frame.ts +4 -0
  136. package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
  137. package/src/mux/task-composer.ts +21 -14
  138. package/src/mux/task-focused-pane.ts +118 -117
  139. package/src/mux/task-screen-keybindings.ts +10 -101
  140. package/src/mux/workspace-rail-model.ts +270 -104
  141. package/src/mux/workspace-rail.ts +45 -22
  142. package/src/pty/session-broker.ts +1 -1
  143. package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
  144. package/src/services/control-plane.ts +50 -32
  145. package/src/services/conversation-lifecycle.ts +118 -87
  146. package/src/services/conversation-startup-hydration.ts +20 -12
  147. package/src/services/directory-hydration.ts +21 -16
  148. package/src/services/event-persistence.ts +7 -0
  149. package/src/services/left-rail-pointer-handler.ts +329 -0
  150. package/src/services/mux-ui-state-persistence.ts +5 -1
  151. package/src/services/recording.ts +34 -26
  152. package/src/services/runtime-command-menu-agent-tools.ts +1 -1
  153. package/src/services/runtime-control-actions.ts +79 -61
  154. package/src/services/runtime-control-plane-ops.ts +122 -83
  155. package/src/services/runtime-conversation-actions.ts +40 -26
  156. package/src/services/runtime-conversation-activation.ts +73 -46
  157. package/src/services/runtime-conversation-starter.ts +53 -45
  158. package/src/services/runtime-conversation-title-edit.ts +91 -80
  159. package/src/services/runtime-envelope-handler.ts +107 -105
  160. package/src/services/runtime-git-state.ts +42 -29
  161. package/src/services/runtime-layout-resize.ts +3 -1
  162. package/src/services/runtime-left-rail-render.ts +99 -63
  163. package/src/services/runtime-nim-cli-session.ts +438 -0
  164. package/src/services/runtime-nim-session.ts +705 -0
  165. package/src/services/runtime-nim-tool-bridge.ts +141 -0
  166. package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
  167. package/src/services/runtime-process-wiring.ts +29 -36
  168. package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
  169. package/src/services/runtime-render-flush.ts +63 -70
  170. package/src/services/runtime-render-lifecycle.ts +65 -64
  171. package/src/services/runtime-render-orchestrator.ts +55 -45
  172. package/src/services/runtime-render-pipeline.ts +106 -103
  173. package/src/services/runtime-render-state.ts +62 -49
  174. package/src/services/runtime-repository-actions.ts +97 -72
  175. package/src/services/runtime-right-pane-render.ts +80 -53
  176. package/src/services/runtime-shutdown.ts +38 -35
  177. package/src/services/runtime-stream-subscriptions.ts +35 -27
  178. package/src/services/runtime-task-composer-persistence.ts +71 -59
  179. package/src/services/runtime-task-composer-snapshot.ts +14 -0
  180. package/src/services/runtime-task-editor-actions.ts +46 -29
  181. package/src/services/runtime-task-pane-actions.ts +220 -134
  182. package/src/services/runtime-task-pane-shortcuts.ts +323 -123
  183. package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
  184. package/src/services/runtime-workspace-observed-events.ts +33 -184
  185. package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
  186. package/src/services/session-diagnostics-store.ts +217 -0
  187. package/src/services/startup-background-resume.ts +26 -21
  188. package/src/services/startup-orchestrator.ts +16 -13
  189. package/src/services/startup-paint-tracker.ts +29 -21
  190. package/src/services/startup-persisted-conversation-queue.ts +19 -13
  191. package/src/services/startup-settled-gate.ts +25 -15
  192. package/src/services/startup-shutdown.ts +18 -22
  193. package/src/services/startup-state-hydration.ts +44 -34
  194. package/src/services/startup-visibility.ts +12 -4
  195. package/src/services/task-pane-selection-actions.ts +89 -72
  196. package/src/services/task-planning-hydration.ts +24 -18
  197. package/src/services/task-planning-observed-events.ts +50 -52
  198. package/src/services/workspace-observed-events.ts +66 -63
  199. package/src/storage/storage-lifecycle-core.ts +438 -0
  200. package/src/store/control-plane-store-normalize.ts +33 -242
  201. package/src/store/control-plane-store-types.ts +1 -35
  202. package/src/store/control-plane-store.ts +360 -56
  203. package/src/store/event-store.ts +366 -8
  204. package/src/terminal/snapshot-oracle.ts +207 -94
  205. package/src/ui/mux-theme.ts +112 -8
  206. package/src/ui/panes/home-gridfire.ts +40 -31
  207. package/src/ui/panes/home.ts +10 -2
  208. package/src/ui/panes/nim.ts +315 -0
  209. package/src/mux/live-mux/actions-task.ts +0 -115
  210. package/src/mux/live-mux/left-rail-actions.ts +0 -118
  211. package/src/mux/live-mux/left-rail-conversation-click.ts +0 -85
  212. package/src/mux/live-mux/left-rail-pointer.ts +0 -74
  213. package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
  214. package/src/services/runtime-directory-actions.ts +0 -164
  215. package/src/services/runtime-input-pipeline.ts +0 -50
  216. package/src/services/runtime-input-router.ts +0 -195
  217. package/src/services/runtime-main-pane-input.ts +0 -230
  218. package/src/services/runtime-modal-input.ts +0 -137
  219. package/src/services/runtime-navigation-input.ts +0 -197
  220. package/src/services/runtime-rail-input.ts +0 -279
  221. package/src/services/runtime-task-pane.ts +0 -62
  222. package/src/services/runtime-workspace-actions.ts +0 -158
  223. package/src/ui/conversation-input-forwarder.ts +0 -114
  224. package/src/ui/conversation-selection-input.ts +0 -103
  225. package/src/ui/global-shortcut-input.ts +0 -89
  226. package/src/ui/input.ts +0 -269
  227. package/src/ui/kit.ts +0 -509
  228. package/src/ui/left-nav-input.ts +0 -80
  229. package/src/ui/left-rail-pointer-input.ts +0 -148
  230. package/src/ui/modals/manager.ts +0 -218
  231. package/src/ui/repository-fold-input.ts +0 -91
  232. package/src/ui/surface.ts +0 -224
@@ -1,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,85 +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
- if (conversationClick.doubleClick) {
50
- options.queueActivateConversationAndEdit(options.selectedConversationId);
51
- } else {
52
- options.queueActivateConversation(options.selectedConversationId);
53
- }
54
- } else if (conversationClick.doubleClick) {
55
- options.beginConversationTitleEdit(options.selectedConversationId);
56
- }
57
- options.markDirty();
58
- return true;
59
- }
60
-
61
- if (options.selectedConversationId !== null) {
62
- if (conversationClick.doubleClick) {
63
- options.queueActivateConversationAndEdit(options.selectedConversationId);
64
- } else {
65
- options.queueActivateConversation(options.selectedConversationId);
66
- }
67
- options.markDirty();
68
- return true;
69
- }
70
-
71
- if (
72
- options.selectedConversationId === null &&
73
- options.selectedProjectId !== null &&
74
- options.directoriesHas(options.selectedProjectId)
75
- ) {
76
- options.setConversationClickState(null);
77
- options.enterProjectPane(options.selectedProjectId);
78
- options.markDirty();
79
- return true;
80
- }
81
-
82
- options.setConversationClickState(null);
83
- options.markDirty();
84
- return true;
85
- }
@@ -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
- }