@jmoyers/harness 0.1.10 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +31 -35
  2. package/package.json +31 -11
  3. package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
  4. package/packages/harness-ai/src/stream-text.ts +13 -91
  5. package/packages/harness-ui/src/frame-primitives.ts +158 -0
  6. package/packages/harness-ui/src/index.ts +18 -0
  7. package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
  8. package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
  9. package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
  10. package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
  11. package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
  12. package/packages/harness-ui/src/interaction/input.ts +420 -0
  13. package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
  14. package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
  15. package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
  16. package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
  17. package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
  18. package/packages/harness-ui/src/kit.ts +476 -0
  19. package/packages/harness-ui/src/layout.ts +238 -0
  20. package/{src/ui/modals/manager.ts → packages/harness-ui/src/modal-manager.ts} +94 -64
  21. package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
  22. package/packages/harness-ui/src/surface.ts +252 -0
  23. package/packages/harness-ui/src/text-layout.ts +210 -0
  24. package/packages/nim-core/src/contracts.ts +239 -0
  25. package/packages/nim-core/src/event-store.ts +299 -0
  26. package/packages/nim-core/src/events.ts +53 -0
  27. package/packages/nim-core/src/index.ts +9 -0
  28. package/packages/nim-core/src/provider-router.ts +129 -0
  29. package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
  30. package/packages/nim-core/src/runtime-factory.ts +49 -0
  31. package/packages/nim-core/src/runtime.ts +1797 -0
  32. package/packages/nim-core/src/session-store.ts +516 -0
  33. package/packages/nim-core/src/telemetry.ts +48 -0
  34. package/packages/nim-test-tui/src/index.ts +150 -0
  35. package/packages/nim-ui-core/src/index.ts +1 -0
  36. package/packages/nim-ui-core/src/projection.ts +87 -0
  37. package/scripts/codex-live-mux-runtime.ts +2 -3721
  38. package/scripts/control-plane-daemon.ts +24 -2
  39. package/scripts/harness-bin.js +5 -0
  40. package/scripts/harness-commands.ts +300 -0
  41. package/scripts/harness-runtime.ts +82 -0
  42. package/scripts/harness.ts +33 -3007
  43. package/scripts/nim-tui-smoke.ts +748 -0
  44. package/src/cli/auth/runtime.ts +948 -0
  45. package/src/cli/default-gateway-pointer.ts +193 -0
  46. package/src/cli/gateway/runtime.ts +1872 -0
  47. package/src/cli/parsing/flags.ts +23 -0
  48. package/src/cli/parsing/session.ts +42 -0
  49. package/src/cli/runtime/context.ts +193 -0
  50. package/src/cli/runtime-app/application.ts +392 -0
  51. package/src/cli/runtime-infra/gateway-control.ts +729 -0
  52. package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
  53. package/src/cli/workflows/runtime.ts +965 -0
  54. package/src/clients/tui/left-rail-interactions.ts +519 -0
  55. package/src/clients/tui/main-pane-interactions.ts +509 -0
  56. package/src/clients/tui/modal-input-routing.ts +71 -0
  57. package/src/clients/tui/render-snapshot-adapter.ts +88 -0
  58. package/src/clients/web/synced-selectors.ts +132 -0
  59. package/src/codex/live-session.ts +82 -29
  60. package/src/config/config-core.ts +361 -10
  61. package/src/config/harness-paths.ts +4 -7
  62. package/src/config/harness-runtime-migration.ts +142 -19
  63. package/src/config/harness.config.template.jsonc +33 -0
  64. package/src/config/secrets-core.ts +92 -4
  65. package/src/control-plane/agent-realtime-api.ts +82 -427
  66. package/src/control-plane/prompt/thread-title-namer.ts +49 -23
  67. package/src/control-plane/session-summary.ts +10 -81
  68. package/src/control-plane/status/reducer-base.ts +12 -12
  69. package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
  70. package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
  71. package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
  72. package/src/control-plane/stream-client.ts +12 -2
  73. package/src/control-plane/stream-command-parser.ts +83 -143
  74. package/src/control-plane/stream-protocol.ts +53 -37
  75. package/src/control-plane/stream-server-background.ts +18 -2
  76. package/src/control-plane/stream-server-command.ts +376 -69
  77. package/src/control-plane/stream-server-session-runtime.ts +3 -2
  78. package/src/control-plane/stream-server.ts +943 -80
  79. package/src/control-plane/stream-session-runtime-types.ts +41 -0
  80. package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
  81. package/src/core/state/observed-stream-cursor.ts +43 -0
  82. package/src/core/state/synced-observed-state.ts +273 -0
  83. package/src/core/store/harness-synced-store.ts +81 -0
  84. package/src/diff/budget.ts +136 -0
  85. package/src/diff/build.ts +289 -0
  86. package/src/diff/chunker.ts +146 -0
  87. package/src/diff/git-invoke.ts +315 -0
  88. package/src/diff/git-parse.ts +472 -0
  89. package/src/diff/hash.ts +70 -0
  90. package/src/diff/index.ts +24 -0
  91. package/src/diff/normalize.ts +134 -0
  92. package/src/diff/types.ts +178 -0
  93. package/src/diff-ui/args.ts +346 -0
  94. package/src/diff-ui/commands.ts +123 -0
  95. package/src/diff-ui/finder.ts +94 -0
  96. package/src/diff-ui/highlight.ts +127 -0
  97. package/src/diff-ui/index.ts +2 -0
  98. package/src/diff-ui/model.ts +141 -0
  99. package/src/diff-ui/pager.ts +412 -0
  100. package/src/diff-ui/render.ts +337 -0
  101. package/src/diff-ui/runtime.ts +379 -0
  102. package/src/diff-ui/state.ts +224 -0
  103. package/src/diff-ui/types.ts +236 -0
  104. package/src/domain/conversations.ts +11 -7
  105. package/src/domain/workspace.ts +76 -4
  106. package/src/mux/control-plane-op-queue.ts +93 -7
  107. package/src/mux/conversation-rail.ts +28 -71
  108. package/src/mux/dual-pane-core.ts +13 -13
  109. package/src/mux/harness-core-ui.ts +313 -42
  110. package/src/mux/input-shortcuts.ts +22 -112
  111. package/src/mux/keybinding-catalog.ts +340 -0
  112. package/src/mux/keybinding-registry.ts +103 -0
  113. package/src/mux/live-mux/command-menu-open-in.ts +280 -0
  114. package/src/mux/live-mux/command-menu.ts +167 -4
  115. package/src/mux/live-mux/conversation-state.ts +13 -0
  116. package/src/mux/live-mux/directory-resolution.ts +1 -1
  117. package/src/mux/live-mux/git-parsing.ts +16 -0
  118. package/src/mux/live-mux/git-snapshot.ts +33 -2
  119. package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
  120. package/src/mux/live-mux/home-pane-drop.ts +1 -1
  121. package/src/mux/live-mux/home-pane-pointer.ts +10 -0
  122. package/src/mux/live-mux/input-forwarding.ts +59 -2
  123. package/src/mux/live-mux/left-nav-activation.ts +124 -7
  124. package/src/mux/live-mux/left-nav.ts +35 -0
  125. package/src/mux/live-mux/link-click.ts +292 -0
  126. package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
  127. package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
  128. package/src/mux/live-mux/modal-input-reducers.ts +106 -8
  129. package/src/mux/live-mux/modal-overlays.ts +210 -31
  130. package/src/mux/live-mux/modal-pointer.ts +3 -7
  131. package/src/mux/live-mux/modal-prompt-handlers.ts +107 -1
  132. package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
  133. package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
  134. package/src/mux/live-mux/pointer-routing.ts +5 -2
  135. package/src/mux/live-mux/project-pane-pointer.ts +8 -0
  136. package/src/mux/live-mux/rail-layout.ts +33 -30
  137. package/src/mux/live-mux/release-notes.ts +383 -0
  138. package/src/mux/live-mux/render-trace-analysis.ts +52 -7
  139. package/src/mux/live-mux/repository-folding.ts +3 -0
  140. package/src/mux/live-mux/selection.ts +0 -4
  141. package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
  142. package/src/mux/project-pane-github-review.ts +271 -0
  143. package/src/mux/render-frame.ts +4 -0
  144. package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
  145. package/src/mux/task-composer.ts +21 -14
  146. package/src/mux/task-focused-pane.ts +118 -117
  147. package/src/mux/task-screen-keybindings.ts +19 -82
  148. package/src/mux/workspace-rail-model.ts +270 -104
  149. package/src/mux/workspace-rail.ts +45 -22
  150. package/src/pty/session-broker.ts +1 -1
  151. package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
  152. package/src/services/control-plane.ts +50 -32
  153. package/src/services/conversation-lifecycle.ts +118 -87
  154. package/src/services/conversation-startup-hydration.ts +20 -12
  155. package/src/services/directory-hydration.ts +21 -16
  156. package/src/services/event-persistence.ts +7 -0
  157. package/src/services/left-rail-pointer-handler.ts +329 -0
  158. package/src/services/mux-ui-state-persistence.ts +5 -1
  159. package/src/services/recording.ts +34 -26
  160. package/src/services/runtime-command-menu-agent-tools.ts +1 -1
  161. package/src/services/runtime-control-actions.ts +79 -61
  162. package/src/services/runtime-control-plane-ops.ts +122 -83
  163. package/src/services/runtime-conversation-actions.ts +40 -26
  164. package/src/services/runtime-conversation-activation.ts +82 -30
  165. package/src/services/runtime-conversation-starter.ts +80 -48
  166. package/src/services/runtime-conversation-title-edit.ts +91 -80
  167. package/src/services/runtime-envelope-handler.ts +107 -105
  168. package/src/services/runtime-git-state.ts +42 -29
  169. package/src/services/runtime-layout-resize.ts +3 -1
  170. package/src/services/runtime-left-rail-render.ts +99 -63
  171. package/src/services/runtime-nim-cli-session.ts +438 -0
  172. package/src/services/runtime-nim-session.ts +705 -0
  173. package/src/services/runtime-nim-tool-bridge.ts +141 -0
  174. package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
  175. package/src/services/runtime-process-wiring.ts +29 -36
  176. package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
  177. package/src/services/runtime-render-flush.ts +63 -70
  178. package/src/services/runtime-render-lifecycle.ts +65 -64
  179. package/src/services/runtime-render-orchestrator.ts +55 -45
  180. package/src/services/runtime-render-pipeline.ts +106 -103
  181. package/src/services/runtime-render-state.ts +62 -49
  182. package/src/services/runtime-repository-actions.ts +97 -70
  183. package/src/services/runtime-right-pane-render.ts +80 -53
  184. package/src/services/runtime-shutdown.ts +38 -35
  185. package/src/services/runtime-stream-subscriptions.ts +35 -27
  186. package/src/services/runtime-task-composer-persistence.ts +71 -59
  187. package/src/services/runtime-task-composer-snapshot.ts +14 -0
  188. package/src/services/runtime-task-editor-actions.ts +46 -29
  189. package/src/services/runtime-task-pane-actions.ts +220 -134
  190. package/src/services/runtime-task-pane-shortcuts.ts +323 -123
  191. package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
  192. package/src/services/runtime-workspace-observed-events.ts +33 -184
  193. package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
  194. package/src/services/session-diagnostics-store.ts +217 -0
  195. package/src/services/startup-background-resume.ts +26 -21
  196. package/src/services/startup-orchestrator.ts +16 -13
  197. package/src/services/startup-paint-tracker.ts +29 -21
  198. package/src/services/startup-persisted-conversation-queue.ts +19 -13
  199. package/src/services/startup-settled-gate.ts +25 -15
  200. package/src/services/startup-shutdown.ts +18 -22
  201. package/src/services/startup-state-hydration.ts +44 -34
  202. package/src/services/startup-visibility.ts +12 -4
  203. package/src/services/task-pane-selection-actions.ts +89 -72
  204. package/src/services/task-planning-hydration.ts +24 -18
  205. package/src/services/task-planning-observed-events.ts +50 -52
  206. package/src/services/workspace-observed-events.ts +66 -63
  207. package/src/storage/storage-lifecycle-core.ts +438 -0
  208. package/src/store/control-plane-store-normalize.ts +33 -242
  209. package/src/store/control-plane-store-types.ts +1 -35
  210. package/src/store/control-plane-store.ts +396 -56
  211. package/src/store/event-store.ts +397 -3
  212. package/src/terminal/snapshot-oracle.ts +207 -94
  213. package/src/ui/mux-theme.ts +112 -8
  214. package/src/ui/panes/home-gridfire.ts +40 -31
  215. package/src/ui/panes/home.ts +10 -2
  216. package/src/ui/panes/nim.ts +315 -0
  217. package/src/mux/live-mux/actions-task.ts +0 -115
  218. package/src/mux/live-mux/left-rail-actions.ts +0 -118
  219. package/src/mux/live-mux/left-rail-conversation-click.ts +0 -82
  220. package/src/mux/live-mux/left-rail-pointer.ts +0 -74
  221. package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
  222. package/src/services/runtime-directory-actions.ts +0 -164
  223. package/src/services/runtime-input-pipeline.ts +0 -50
  224. package/src/services/runtime-input-router.ts +0 -189
  225. package/src/services/runtime-main-pane-input.ts +0 -230
  226. package/src/services/runtime-modal-input.ts +0 -119
  227. package/src/services/runtime-navigation-input.ts +0 -197
  228. package/src/services/runtime-rail-input.ts +0 -278
  229. package/src/services/runtime-task-pane.ts +0 -62
  230. package/src/services/runtime-workspace-actions.ts +0 -158
  231. package/src/ui/conversation-input-forwarder.ts +0 -114
  232. package/src/ui/conversation-selection-input.ts +0 -103
  233. package/src/ui/global-shortcut-input.ts +0 -89
  234. package/src/ui/input.ts +0 -238
  235. package/src/ui/kit.ts +0 -509
  236. package/src/ui/left-nav-input.ts +0 -80
  237. package/src/ui/left-rail-pointer-input.ts +0 -148
  238. package/src/ui/repository-fold-input.ts +0 -91
  239. package/src/ui/surface.ts +0 -224
@@ -0,0 +1,519 @@
1
+ import {
2
+ detectMuxGlobalShortcut,
3
+ type ResolvedMuxShortcutBindings,
4
+ } from '../../mux/input-shortcuts.ts';
5
+ import { handleGlobalShortcut } from '../../mux/live-mux/global-shortcut-handlers.ts';
6
+ import {
7
+ activateLeftNavTarget,
8
+ cycleLeftNavSelection,
9
+ } from '../../mux/live-mux/left-nav-activation.ts';
10
+ import { visibleLeftNavTargets } from '../../mux/live-mux/left-nav.ts';
11
+ import {
12
+ firstDirectoryForRepositoryGroup as firstDirectoryForRepositoryGroupFn,
13
+ reduceRepositoryFoldChordInput,
14
+ repositoryTreeArrowAction,
15
+ } from '../../mux/live-mux/repository-folding.ts';
16
+ import type { buildWorkspaceRailViewRows } from '../../mux/workspace-rail-model.ts';
17
+ import type { WorkspaceModel } from '../../domain/workspace.ts';
18
+ import { LeftRailPointerHandler } from '../../services/left-rail-pointer-handler.ts';
19
+ import { GlobalShortcutInput } from '../../../packages/harness-ui/src/interaction/global-shortcut-input.ts';
20
+ import { LeftNavInput } from '../../../packages/harness-ui/src/interaction/left-nav-input.ts';
21
+ import {
22
+ RailPointerInput,
23
+ type HandlePointerClickInput,
24
+ } from '../../../packages/harness-ui/src/interaction/rail-pointer-input.ts';
25
+ import { RepositoryFoldInput } from '../../../packages/harness-ui/src/interaction/repository-fold-input.ts';
26
+
27
+ interface DirectoryRecordLike {
28
+ readonly directoryId: string;
29
+ }
30
+
31
+ interface ConversationRecordLike {
32
+ readonly directoryId: string | null;
33
+ readonly agentType: string;
34
+ }
35
+
36
+ interface ConversationLookup {
37
+ readonly activeConversationId: string | null;
38
+ has(sessionId: string): boolean;
39
+ directoryIdOf(sessionId: string): string | null;
40
+ }
41
+
42
+ interface DirectoryLookup {
43
+ hasDirectory(directoryId: string): boolean;
44
+ }
45
+
46
+ interface RepositoryManagerLike {
47
+ repositoryGroupIdForDirectory(directoryId: string, fallbackGroupId: string): string;
48
+ collapseRepositoryGroup(repositoryGroupId: string, repositoriesCollapsed: boolean): void;
49
+ expandRepositoryGroup(repositoryGroupId: string, repositoriesCollapsed: boolean): void;
50
+ toggleRepositoryGroup(repositoryGroupId: string, repositoriesCollapsed: boolean): void;
51
+ collapseAllRepositoryGroups(): true;
52
+ expandAllRepositoryGroups(): false;
53
+ }
54
+
55
+ interface QueueControlPlaneOps {
56
+ queueControlPlaneOp(task: () => Promise<void>, label: string): void;
57
+ queueLatestControlPlaneOp(
58
+ key: string,
59
+ task: (options: { readonly signal: AbortSignal }) => Promise<void>,
60
+ label: string,
61
+ ): void;
62
+ }
63
+
64
+ interface ConversationLifecycleActions {
65
+ activateConversation(
66
+ sessionId: string,
67
+ options?: {
68
+ readonly signal?: AbortSignal;
69
+ },
70
+ ): Promise<void>;
71
+ openOrCreateCritiqueConversationInDirectory(directoryId: string): Promise<void>;
72
+ takeoverConversation(sessionId: string): Promise<void>;
73
+ beginConversationTitleEdit(conversationId: string): void;
74
+ stopConversationTitleEdit(persistPending: boolean): void;
75
+ }
76
+
77
+ interface RuntimeDirectoryActions {
78
+ archiveConversation(sessionId: string): Promise<void>;
79
+ closeDirectory(directoryId: string): Promise<void>;
80
+ }
81
+
82
+ interface RuntimeRepositoryActions {
83
+ openRepositoryPromptForCreate(): void;
84
+ openRepositoryPromptForEdit(repositoryId: string): void;
85
+ archiveRepositoryById(repositoryId: string): Promise<void>;
86
+ }
87
+
88
+ interface RuntimeControlActions {
89
+ toggleGatewayProfiler(): Promise<void>;
90
+ toggleGatewayStatusTimeline(): Promise<void>;
91
+ toggleGatewayRenderTrace(conversationId: string | null): Promise<void>;
92
+ refreshAllConversationTitles(): Promise<void>;
93
+ interruptConversation(sessionId: string): Promise<void>;
94
+ }
95
+
96
+ interface TuiLeftRailInteractionNavigationActions {
97
+ enterHomePane(): void;
98
+ enterNimPane?(): void;
99
+ enterProjectPane(directoryId: string): void;
100
+ enterTasksPane?(): void;
101
+ resolveDirectoryForAction(): string | null;
102
+ openNewThreadPrompt(directoryId: string): void;
103
+ toggleCommandMenu(): void;
104
+ requestStop(): void;
105
+ markDirty(): void;
106
+ queuePersistMuxUiState(): void;
107
+ resetFrameCache(): void;
108
+ releaseViewportPinForSelection(): void;
109
+ }
110
+
111
+ export interface TuiLeftRailInteractionOptions<
112
+ TShortcutBindings extends ResolvedMuxShortcutBindings | undefined,
113
+ TDirectoryRecord extends DirectoryRecordLike,
114
+ TConversationRecord extends ConversationRecordLike,
115
+ > {
116
+ readonly workspace: WorkspaceModel;
117
+ readonly railViewState: {
118
+ readLatestRows(): ReturnType<typeof buildWorkspaceRailViewRows>;
119
+ };
120
+ readonly directories: ReadonlyMap<string, TDirectoryRecord>;
121
+ readonly conversationRecords: ReadonlyMap<string, TConversationRecord>;
122
+ readonly repositories: ReadonlyMap<string, unknown>;
123
+ readonly conversationLookup: ConversationLookup;
124
+ readonly directoryLookup: DirectoryLookup;
125
+ readonly repositoryManager: RepositoryManagerLike;
126
+ readonly repositoryGroupFallbackId: string;
127
+ readonly queueControlPlaneOps: QueueControlPlaneOps;
128
+ readonly conversationLifecycle: ConversationLifecycleActions;
129
+ readonly runtimeDirectoryActions: RuntimeDirectoryActions;
130
+ readonly runtimeRepositoryActions: RuntimeRepositoryActions;
131
+ readonly runtimeControlActions: RuntimeControlActions;
132
+ readonly navigation: TuiLeftRailInteractionNavigationActions;
133
+ readonly shortcutBindings: TShortcutBindings;
134
+ readonly showTasksEntry: boolean;
135
+ readonly nowMs?: () => number;
136
+ readonly repositoryToggleChordTimeoutMs?: number;
137
+ readonly repositoryCollapseAllChordPrefix?: Buffer;
138
+ readonly conversationTitleEditDoubleClickWindowMs?: number;
139
+ }
140
+
141
+ export interface TuiLeftRailInteractions {
142
+ readonly handleRepositoryFoldInput: (input: Buffer) => boolean;
143
+ readonly handleGlobalShortcutInput: (input: Buffer) => boolean;
144
+ readonly leftRailPointerInput: {
145
+ handlePointerClick(input: HandlePointerClickInput): boolean;
146
+ };
147
+ }
148
+
149
+ const DEFAULT_CONVERSATION_TITLE_EDIT_DOUBLE_CLICK_WINDOW_MS = 350;
150
+ const DEFAULT_REPOSITORY_TOGGLE_CHORD_TIMEOUT_MS = 1250;
151
+ const DEFAULT_REPOSITORY_COLLAPSE_ALL_CHORD_PREFIX = Buffer.from([0x0b]);
152
+
153
+ export function createTuiLeftRailInteractions<
154
+ TShortcutBindings extends ResolvedMuxShortcutBindings | undefined,
155
+ TDirectoryRecord extends DirectoryRecordLike,
156
+ TConversationRecord extends ConversationRecordLike,
157
+ >(
158
+ options: TuiLeftRailInteractionOptions<TShortcutBindings, TDirectoryRecord, TConversationRecord>,
159
+ ): TuiLeftRailInteractions {
160
+ const nowMs = options.nowMs ?? (() => Date.now());
161
+ const conversationTitleEditDoubleClickWindowMs =
162
+ options.conversationTitleEditDoubleClickWindowMs ??
163
+ DEFAULT_CONVERSATION_TITLE_EDIT_DOUBLE_CLICK_WINDOW_MS;
164
+ const repositoryToggleChordTimeoutMs =
165
+ options.repositoryToggleChordTimeoutMs ?? DEFAULT_REPOSITORY_TOGGLE_CHORD_TIMEOUT_MS;
166
+ const repositoryCollapseAllChordPrefix =
167
+ options.repositoryCollapseAllChordPrefix ?? DEFAULT_REPOSITORY_COLLAPSE_ALL_CHORD_PREFIX;
168
+
169
+ const selectLeftNavRepository = options.workspace.selectLeftNavRepository.bind(options.workspace);
170
+ const selectLeftNavConversation = options.workspace.selectLeftNavConversation.bind(
171
+ options.workspace,
172
+ );
173
+ const repositoryGroupIdForDirectory = (directoryId: string): string => {
174
+ return options.repositoryManager.repositoryGroupIdForDirectory(
175
+ directoryId,
176
+ options.repositoryGroupFallbackId,
177
+ );
178
+ };
179
+
180
+ const firstDirectoryForRepositoryGroup = (repositoryGroupId: string): string | null => {
181
+ return firstDirectoryForRepositoryGroupFn(
182
+ options.directories,
183
+ repositoryGroupIdForDirectory,
184
+ repositoryGroupId,
185
+ );
186
+ };
187
+
188
+ const collapseRepositoryGroup = (repositoryGroupId: string): void => {
189
+ options.repositoryManager.collapseRepositoryGroup(
190
+ repositoryGroupId,
191
+ options.workspace.repositoriesCollapsed,
192
+ );
193
+ };
194
+ const expandRepositoryGroup = (repositoryGroupId: string): void => {
195
+ options.repositoryManager.expandRepositoryGroup(
196
+ repositoryGroupId,
197
+ options.workspace.repositoriesCollapsed,
198
+ );
199
+ };
200
+ const toggleRepositoryGroup = (repositoryGroupId: string): void => {
201
+ options.repositoryManager.toggleRepositoryGroup(
202
+ repositoryGroupId,
203
+ options.workspace.repositoriesCollapsed,
204
+ );
205
+ };
206
+ const collapseAllRepositoryGroups = (): void => {
207
+ options.workspace.repositoriesCollapsed =
208
+ options.repositoryManager.collapseAllRepositoryGroups();
209
+ options.navigation.queuePersistMuxUiState();
210
+ };
211
+ const expandAllRepositoryGroups = (): void => {
212
+ options.workspace.repositoriesCollapsed = options.repositoryManager.expandAllRepositoryGroups();
213
+ options.navigation.queuePersistMuxUiState();
214
+ };
215
+
216
+ const openAddDirectoryPromptFromRail = (): void => {
217
+ options.workspace.repositoryPrompt = null;
218
+ options.workspace.apiKeyPrompt = null;
219
+ options.workspace.addDirectoryPrompt = {
220
+ value: '',
221
+ error: null,
222
+ };
223
+ };
224
+
225
+ const leftNavInput = new LeftNavInput(
226
+ {
227
+ railViewState: options.railViewState,
228
+ currentSelection: () => options.workspace.leftNavSelection,
229
+ },
230
+ {
231
+ enterHomePane: options.navigation.enterHomePane,
232
+ ...(options.navigation.enterNimPane === undefined
233
+ ? {}
234
+ : {
235
+ enterNimPane: options.navigation.enterNimPane,
236
+ }),
237
+ firstDirectoryForRepositoryGroup,
238
+ enterProjectPane: options.navigation.enterProjectPane,
239
+ setMainPaneProjectMode: () => {
240
+ options.workspace.mainPaneMode = 'project';
241
+ },
242
+ selectLeftNavRepository,
243
+ selectLeftNavConversation,
244
+ markDirty: options.navigation.markDirty,
245
+ directoriesHas: (directoryId) => options.directoryLookup.hasDirectory(directoryId),
246
+ conversationDirectoryId: (sessionId) => options.conversationLookup.directoryIdOf(sessionId),
247
+ queueControlPlaneOp: options.queueControlPlaneOps.queueControlPlaneOp,
248
+ queueLatestControlPlaneOp: options.queueControlPlaneOps.queueLatestControlPlaneOp,
249
+ activateConversation: async (sessionId, activateOptions) => {
250
+ await options.conversationLifecycle.activateConversation(sessionId, activateOptions);
251
+ },
252
+ conversationsHas: (sessionId) => options.conversationLookup.has(sessionId),
253
+ ...(options.showTasksEntry
254
+ ? {
255
+ enterTasksPane: options.navigation.enterTasksPane,
256
+ }
257
+ : {}),
258
+ },
259
+ {
260
+ visibleTargets: visibleLeftNavTargets,
261
+ activateTarget: activateLeftNavTarget,
262
+ cycleSelection: cycleLeftNavSelection,
263
+ },
264
+ );
265
+
266
+ const repositoryFoldInput = new RepositoryFoldInput(
267
+ {
268
+ leftNavSelection: () => options.workspace.leftNavSelection,
269
+ repositoryToggleChordPrefixAtMs: () => options.workspace.repositoryToggleChordPrefixAtMs,
270
+ setRepositoryToggleChordPrefixAtMs: (value) => {
271
+ options.workspace.repositoryToggleChordPrefixAtMs = value;
272
+ },
273
+ conversations: () => options.conversationRecords,
274
+ repositoryGroupIdForDirectory,
275
+ nowMs,
276
+ },
277
+ {
278
+ collapseRepositoryGroup,
279
+ expandRepositoryGroup,
280
+ collapseAllRepositoryGroups,
281
+ expandAllRepositoryGroups,
282
+ selectLeftNavRepository,
283
+ markDirty: options.navigation.markDirty,
284
+ },
285
+ {
286
+ chordTimeoutMs: repositoryToggleChordTimeoutMs,
287
+ collapseAllChordPrefix: repositoryCollapseAllChordPrefix,
288
+ },
289
+ {
290
+ reduceRepositoryFoldChordInput,
291
+ repositoryTreeArrowAction,
292
+ },
293
+ );
294
+
295
+ const globalShortcutInput = new GlobalShortcutInput(
296
+ options.shortcutBindings,
297
+ {
298
+ mainPaneMode: () => options.workspace.mainPaneMode,
299
+ activeConversationId: () => options.conversationLookup.activeConversationId,
300
+ activeConversationAgentType: () => {
301
+ const activeConversationId = options.conversationLookup.activeConversationId;
302
+ if (activeConversationId === null) {
303
+ return null;
304
+ }
305
+ return options.conversationRecords.get(activeConversationId)?.agentType ?? null;
306
+ },
307
+ conversationsHas: (sessionId) => options.conversationLookup.has(sessionId),
308
+ activeDirectoryId: () => options.workspace.activeDirectoryId,
309
+ directoryExists: (directoryId) => options.directoryLookup.hasDirectory(directoryId),
310
+ },
311
+ {
312
+ requestStop: options.navigation.requestStop,
313
+ resolveDirectoryForAction: options.navigation.resolveDirectoryForAction,
314
+ openNewThreadPrompt: options.navigation.openNewThreadPrompt,
315
+ toggleCommandMenu: options.navigation.toggleCommandMenu,
316
+ openOrCreateCritiqueConversationInDirectory: async (directoryId) => {
317
+ await options.conversationLifecycle.openOrCreateCritiqueConversationInDirectory(
318
+ directoryId,
319
+ );
320
+ },
321
+ toggleGatewayProfile: async () => {
322
+ await options.runtimeControlActions.toggleGatewayProfiler();
323
+ },
324
+ toggleGatewayStatusTimeline: async () => {
325
+ await options.runtimeControlActions.toggleGatewayStatusTimeline();
326
+ },
327
+ toggleGatewayRenderTrace: async (conversationId) => {
328
+ await options.runtimeControlActions.toggleGatewayRenderTrace(conversationId);
329
+ },
330
+ queueControlPlaneOp: options.queueControlPlaneOps.queueControlPlaneOp,
331
+ archiveConversation: async (sessionId) => {
332
+ await options.runtimeDirectoryActions.archiveConversation(sessionId);
333
+ },
334
+ refreshAllConversationTitles: async () => {
335
+ await options.runtimeControlActions.refreshAllConversationTitles();
336
+ },
337
+ interruptConversation: async (sessionId) => {
338
+ await options.runtimeControlActions.interruptConversation(sessionId);
339
+ },
340
+ takeoverConversation: async (sessionId) => {
341
+ await options.conversationLifecycle.takeoverConversation(sessionId);
342
+ },
343
+ openAddDirectoryPrompt: () => {
344
+ openAddDirectoryPromptFromRail();
345
+ options.navigation.markDirty();
346
+ },
347
+ closeDirectory: async (directoryId) => {
348
+ await options.runtimeDirectoryActions.closeDirectory(directoryId);
349
+ },
350
+ cycleLeftNavSelection: (direction) => {
351
+ leftNavInput.cycleSelection(direction);
352
+ },
353
+ },
354
+ {
355
+ detectShortcut: detectMuxGlobalShortcut,
356
+ handleShortcut: (input) =>
357
+ handleGlobalShortcut({
358
+ ...input,
359
+ toggleDebugBar: () => {
360
+ options.workspace.showDebugBar = !options.workspace.showDebugBar;
361
+ options.navigation.queuePersistMuxUiState();
362
+ options.navigation.markDirty();
363
+ },
364
+ }),
365
+ },
366
+ );
367
+
368
+ const leftRailPointerHandler = new LeftRailPointerHandler(
369
+ {
370
+ railViewState: options.railViewState,
371
+ conversationTitleEditConversationId: () =>
372
+ options.workspace.conversationTitleEdit?.conversationId ?? null,
373
+ activeConversationId: () => options.conversationLookup.activeConversationId,
374
+ repositoriesCollapsed: () => options.workspace.repositoriesCollapsed,
375
+ resolveDirectoryForAction: options.navigation.resolveDirectoryForAction,
376
+ previousConversationClickState: () => options.workspace.conversationTitleEditClickState,
377
+ nowMs,
378
+ isConversationPaneActive: () => options.workspace.mainPaneMode === 'conversation',
379
+ directoriesHas: (directoryId) => options.directoryLookup.hasDirectory(directoryId),
380
+ },
381
+ {
382
+ clearConversationTitleEditClickState: () => {
383
+ options.workspace.conversationTitleEditClickState = null;
384
+ },
385
+ openNewThreadPrompt: options.navigation.openNewThreadPrompt,
386
+ queueArchiveConversation: (conversationId) => {
387
+ options.queueControlPlaneOps.queueControlPlaneOp(async () => {
388
+ await options.runtimeDirectoryActions.archiveConversation(conversationId);
389
+ }, 'mouse-archive-conversation');
390
+ },
391
+ openAddDirectoryPrompt: openAddDirectoryPromptFromRail,
392
+ openRepositoryPromptForCreate: () => {
393
+ options.runtimeRepositoryActions.openRepositoryPromptForCreate();
394
+ },
395
+ repositoryExists: (repositoryId) => options.repositories.has(repositoryId),
396
+ openRepositoryPromptForEdit: (repositoryId) => {
397
+ options.runtimeRepositoryActions.openRepositoryPromptForEdit(repositoryId);
398
+ },
399
+ queueArchiveRepository: (repositoryId) => {
400
+ options.queueControlPlaneOps.queueControlPlaneOp(async () => {
401
+ await options.runtimeRepositoryActions.archiveRepositoryById(repositoryId);
402
+ }, 'mouse-archive-repository');
403
+ },
404
+ toggleRepositoryGroup,
405
+ selectLeftNavRepository,
406
+ expandAllRepositoryGroups,
407
+ collapseAllRepositoryGroups,
408
+ enterHomePane: options.navigation.enterHomePane,
409
+ ...(options.navigation.enterNimPane === undefined
410
+ ? {}
411
+ : {
412
+ enterNimPane: options.navigation.enterNimPane,
413
+ }),
414
+ ...(options.showTasksEntry
415
+ ? {
416
+ enterTasksPane: options.navigation.enterTasksPane,
417
+ }
418
+ : {}),
419
+ queueCloseDirectory: (directoryId) => {
420
+ options.queueControlPlaneOps.queueControlPlaneOp(async () => {
421
+ await options.runtimeDirectoryActions.closeDirectory(directoryId);
422
+ }, 'mouse-close-directory');
423
+ },
424
+ toggleShortcutsCollapsed: () => {
425
+ options.workspace.shortcutsCollapsed = !options.workspace.shortcutsCollapsed;
426
+ options.navigation.queuePersistMuxUiState();
427
+ },
428
+ setConversationClickState: (next) => {
429
+ options.workspace.conversationTitleEditClickState = next;
430
+ },
431
+ ensureConversationPaneActive: (conversationId) => {
432
+ options.workspace.mainPaneMode = 'conversation';
433
+ options.workspace.selectLeftNavConversation(conversationId);
434
+ options.workspace.projectPaneSnapshot = null;
435
+ options.workspace.projectPaneScrollTop = 0;
436
+ options.navigation.resetFrameCache();
437
+ },
438
+ beginConversationTitleEdit: (conversationId) => {
439
+ options.conversationLifecycle.beginConversationTitleEdit(conversationId);
440
+ },
441
+ queueActivateConversation: (conversationId) => {
442
+ options.queueControlPlaneOps.queueLatestControlPlaneOp(
443
+ 'left-nav:activate-conversation',
444
+ async ({ signal }) => {
445
+ if (signal.aborted) {
446
+ return;
447
+ }
448
+ await options.conversationLifecycle.activateConversation(conversationId, {
449
+ signal,
450
+ });
451
+ },
452
+ 'mouse-activate-conversation',
453
+ );
454
+ },
455
+ queueActivateConversationAndEdit: (conversationId) => {
456
+ options.queueControlPlaneOps.queueLatestControlPlaneOp(
457
+ 'left-nav:activate-conversation',
458
+ async ({ signal }) => {
459
+ if (signal.aborted) {
460
+ return;
461
+ }
462
+ await options.conversationLifecycle.activateConversation(conversationId, {
463
+ signal,
464
+ });
465
+ if (signal.aborted) {
466
+ return;
467
+ }
468
+ options.conversationLifecycle.beginConversationTitleEdit(conversationId);
469
+ },
470
+ 'mouse-activate-edit-conversation',
471
+ );
472
+ },
473
+ enterProjectPane: options.navigation.enterProjectPane,
474
+ markDirty: options.navigation.markDirty,
475
+ },
476
+ {
477
+ conversationTitleEditDoubleClickWindowMs,
478
+ },
479
+ );
480
+
481
+ const leftRailPointerInput = new RailPointerInput(
482
+ leftRailPointerHandler,
483
+ leftRailPointerHandler,
484
+ {
485
+ hasActiveEdit: () => options.workspace.conversationTitleEdit !== null,
486
+ shouldKeepActiveEdit: (hit) =>
487
+ leftRailPointerHandler.shouldKeepConversationTitleEditActive(hit),
488
+ stopActiveEdit: () => {
489
+ options.conversationLifecycle.stopConversationTitleEdit(true);
490
+ },
491
+ },
492
+ {
493
+ hasSelection: () =>
494
+ options.workspace.selection !== null || options.workspace.selectionDrag !== null,
495
+ clearSelection: () => {
496
+ options.workspace.selection = null;
497
+ options.workspace.selectionDrag = null;
498
+ options.navigation.releaseViewportPinForSelection();
499
+ },
500
+ },
501
+ );
502
+
503
+ const handleRepositoryFoldInput = (input: Buffer): boolean => {
504
+ return (
505
+ repositoryFoldInput.handleRepositoryFoldChords(input) ||
506
+ repositoryFoldInput.handleRepositoryTreeArrow(input)
507
+ );
508
+ };
509
+
510
+ const handleGlobalShortcutInput = (input: Buffer): boolean => {
511
+ return globalShortcutInput.handleInput(input);
512
+ };
513
+
514
+ return {
515
+ handleRepositoryFoldInput,
516
+ handleGlobalShortcutInput,
517
+ leftRailPointerInput,
518
+ };
519
+ }