@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,148 +0,0 @@
1
- import { handleLeftRailActionClick } from '../mux/live-mux/left-rail-actions.ts';
2
- import { handleLeftRailConversationClick } from '../mux/live-mux/left-rail-conversation-click.ts';
3
- import {
4
- handleLeftRailPointerClick,
5
- type LeftRailPointerContext,
6
- } from '../mux/live-mux/left-rail-pointer.ts';
7
- import type { buildWorkspaceRailViewRows } from '../mux/workspace-rail-model.ts';
8
-
9
- export interface ConversationTitleClickState {
10
- readonly conversationId: string;
11
- readonly atMs: number;
12
- }
13
-
14
- interface LeftRailPointerInputOptions {
15
- readonly getLatestRailRows: () => ReturnType<typeof buildWorkspaceRailViewRows>;
16
- readonly hasConversationTitleEdit: () => boolean;
17
- readonly conversationTitleEditConversationId: () => string | null;
18
- readonly stopConversationTitleEdit: () => void;
19
- readonly hasSelection: () => boolean;
20
- readonly clearSelection: () => void;
21
- readonly activeConversationId: () => string | null;
22
- readonly repositoriesCollapsed: () => boolean;
23
- readonly clearConversationTitleEditClickState: () => void;
24
- readonly resolveDirectoryForAction: () => string | null;
25
- readonly openNewThreadPrompt: (directoryId: string) => void;
26
- readonly queueArchiveConversation: (conversationId: string) => void;
27
- readonly openAddDirectoryPrompt: () => void;
28
- readonly openRepositoryPromptForCreate: () => void;
29
- readonly repositoryExists: (repositoryId: string) => boolean;
30
- readonly openRepositoryPromptForEdit: (repositoryId: string) => void;
31
- readonly queueArchiveRepository: (repositoryId: string) => void;
32
- readonly toggleRepositoryGroup: (repositoryGroupId: string) => void;
33
- readonly selectLeftNavRepository: (repositoryGroupId: string) => void;
34
- readonly expandAllRepositoryGroups: () => void;
35
- readonly collapseAllRepositoryGroups: () => void;
36
- readonly enterHomePane: () => void;
37
- readonly queueCloseDirectory: (directoryId: string) => void;
38
- readonly toggleShortcutsCollapsed: () => void;
39
- readonly previousConversationClickState: () => ConversationTitleClickState | null;
40
- readonly setConversationClickState: (next: ConversationTitleClickState | null) => void;
41
- readonly nowMs: () => number;
42
- readonly conversationTitleEditDoubleClickWindowMs: number;
43
- readonly isConversationPaneActive: () => boolean;
44
- readonly ensureConversationPaneActive: (conversationId: string) => void;
45
- readonly beginConversationTitleEdit: (conversationId: string) => void;
46
- readonly queueActivateConversation: (conversationId: string) => void;
47
- readonly queueActivateConversationAndEdit: (conversationId: string) => void;
48
- readonly directoriesHas: (directoryId: string) => boolean;
49
- readonly enterProjectPane: (directoryId: string) => void;
50
- readonly markDirty: () => void;
51
- }
52
-
53
- interface LeftRailPointerInputDependencies {
54
- readonly handleLeftRailPointerClick?: typeof handleLeftRailPointerClick;
55
- readonly handleLeftRailActionClick?: typeof handleLeftRailActionClick;
56
- readonly handleLeftRailConversationClick?: typeof handleLeftRailConversationClick;
57
- }
58
-
59
- interface HandlePointerClickInput {
60
- readonly clickEligible: boolean;
61
- readonly paneRows: number;
62
- readonly leftCols: number;
63
- readonly pointerRow: number;
64
- readonly pointerCol: number;
65
- }
66
-
67
- export class LeftRailPointerInput {
68
- private readonly pointerClick: typeof handleLeftRailPointerClick;
69
- private readonly actionClick: typeof handleLeftRailActionClick;
70
- private readonly conversationClick: typeof handleLeftRailConversationClick;
71
-
72
- constructor(
73
- private readonly options: LeftRailPointerInputOptions,
74
- dependencies: LeftRailPointerInputDependencies = {},
75
- ) {
76
- this.pointerClick = dependencies.handleLeftRailPointerClick ?? handleLeftRailPointerClick;
77
- this.actionClick = dependencies.handleLeftRailActionClick ?? handleLeftRailActionClick;
78
- this.conversationClick =
79
- dependencies.handleLeftRailConversationClick ?? handleLeftRailConversationClick;
80
- }
81
-
82
- handlePointerClick(input: HandlePointerClickInput): boolean {
83
- return this.pointerClick({
84
- clickEligible: input.clickEligible,
85
- rows: this.options.getLatestRailRows(),
86
- paneRows: input.paneRows,
87
- leftCols: input.leftCols,
88
- pointerRow: input.pointerRow,
89
- pointerCol: input.pointerCol,
90
- hasConversationTitleEdit: this.options.hasConversationTitleEdit(),
91
- conversationTitleEditConversationId: this.options.conversationTitleEditConversationId(),
92
- stopConversationTitleEdit: this.options.stopConversationTitleEdit,
93
- hasSelection: this.options.hasSelection(),
94
- clearSelection: this.options.clearSelection,
95
- handleAction: (context) => this.handleAction(context),
96
- handleConversation: (context) => this.handleConversation(context),
97
- });
98
- }
99
-
100
- private handleAction(context: LeftRailPointerContext): boolean {
101
- return this.actionClick({
102
- action: context.selectedAction,
103
- selectedProjectId: context.selectedProjectId,
104
- selectedRepositoryId: context.selectedRepositoryId,
105
- activeConversationId: this.options.activeConversationId(),
106
- repositoriesCollapsed: this.options.repositoriesCollapsed(),
107
- clearConversationTitleEditClickState: this.options.clearConversationTitleEditClickState,
108
- resolveDirectoryForAction: this.options.resolveDirectoryForAction,
109
- openNewThreadPrompt: this.options.openNewThreadPrompt,
110
- queueArchiveConversation: this.options.queueArchiveConversation,
111
- openAddDirectoryPrompt: this.options.openAddDirectoryPrompt,
112
- openRepositoryPromptForCreate: this.options.openRepositoryPromptForCreate,
113
- repositoryExists: this.options.repositoryExists,
114
- openRepositoryPromptForEdit: this.options.openRepositoryPromptForEdit,
115
- queueArchiveRepository: this.options.queueArchiveRepository,
116
- toggleRepositoryGroup: this.options.toggleRepositoryGroup,
117
- selectLeftNavRepository: this.options.selectLeftNavRepository,
118
- expandAllRepositoryGroups: this.options.expandAllRepositoryGroups,
119
- collapseAllRepositoryGroups: this.options.collapseAllRepositoryGroups,
120
- enterHomePane: this.options.enterHomePane,
121
- queueCloseDirectory: this.options.queueCloseDirectory,
122
- toggleShortcutsCollapsed: this.options.toggleShortcutsCollapsed,
123
- markDirty: this.options.markDirty,
124
- });
125
- }
126
-
127
- private handleConversation(context: LeftRailPointerContext): void {
128
- this.conversationClick({
129
- selectedConversationId: context.selectedConversationId,
130
- selectedProjectId: context.selectedProjectId,
131
- supportsConversationTitleEditClick: context.supportsConversationTitleEditClick,
132
- previousClickState: this.options.previousConversationClickState(),
133
- nowMs: this.options.nowMs(),
134
- conversationTitleEditDoubleClickWindowMs:
135
- this.options.conversationTitleEditDoubleClickWindowMs,
136
- activeConversationId: this.options.activeConversationId(),
137
- isConversationPaneActive: this.options.isConversationPaneActive(),
138
- setConversationClickState: this.options.setConversationClickState,
139
- ensureConversationPaneActive: this.options.ensureConversationPaneActive,
140
- beginConversationTitleEdit: this.options.beginConversationTitleEdit,
141
- queueActivateConversation: this.options.queueActivateConversation,
142
- queueActivateConversationAndEdit: this.options.queueActivateConversationAndEdit,
143
- directoriesHas: this.options.directoriesHas,
144
- enterProjectPane: this.options.enterProjectPane,
145
- markDirty: this.options.markDirty,
146
- });
147
- }
148
- }
@@ -1,91 +0,0 @@
1
- import {
2
- reduceRepositoryFoldChordInput,
3
- repositoryTreeArrowAction,
4
- } from '../mux/live-mux/repository-folding.ts';
5
- import type { LeftNavSelection } from '../mux/live-mux/left-nav.ts';
6
-
7
- interface RepositoryFoldInputOptions {
8
- readonly getLeftNavSelection: () => LeftNavSelection;
9
- readonly getRepositoryToggleChordPrefixAtMs: () => number | null;
10
- readonly setRepositoryToggleChordPrefixAtMs: (value: number | null) => void;
11
- readonly conversations: ReadonlyMap<string, { directoryId: string | null }>;
12
- readonly repositoryGroupIdForDirectory: (directoryId: string) => string;
13
- readonly collapseRepositoryGroup: (repositoryGroupId: string) => void;
14
- readonly expandRepositoryGroup: (repositoryGroupId: string) => void;
15
- readonly collapseAllRepositoryGroups: () => void;
16
- readonly expandAllRepositoryGroups: () => void;
17
- readonly selectLeftNavRepository: (repositoryGroupId: string) => void;
18
- readonly markDirty: () => void;
19
- readonly chordTimeoutMs: number;
20
- readonly collapseAllChordPrefix: Buffer;
21
- readonly nowMs: () => number;
22
- }
23
-
24
- export class RepositoryFoldInput {
25
- constructor(private readonly options: RepositoryFoldInputOptions) {}
26
-
27
- private selectedRepositoryGroupId(): string | null {
28
- const leftNavSelection = this.options.getLeftNavSelection();
29
- if (leftNavSelection.kind === 'repository') {
30
- return leftNavSelection.repositoryId;
31
- }
32
- if (leftNavSelection.kind === 'project') {
33
- return this.options.repositoryGroupIdForDirectory(leftNavSelection.directoryId);
34
- }
35
- if (leftNavSelection.kind === 'conversation') {
36
- const conversation = this.options.conversations.get(leftNavSelection.sessionId);
37
- if (conversation?.directoryId !== null && conversation?.directoryId !== undefined) {
38
- return this.options.repositoryGroupIdForDirectory(conversation.directoryId);
39
- }
40
- }
41
- return null;
42
- }
43
-
44
- handleRepositoryTreeArrow(input: Buffer): boolean {
45
- const repositoryId = this.selectedRepositoryGroupId();
46
- const action = repositoryTreeArrowAction(
47
- input,
48
- this.options.getLeftNavSelection(),
49
- repositoryId,
50
- );
51
- if (repositoryId === null || action === null) {
52
- return false;
53
- }
54
- if (action === 'expand') {
55
- this.options.expandRepositoryGroup(repositoryId);
56
- this.options.selectLeftNavRepository(repositoryId);
57
- this.options.markDirty();
58
- return true;
59
- }
60
- if (action === 'collapse') {
61
- this.options.collapseRepositoryGroup(repositoryId);
62
- this.options.selectLeftNavRepository(repositoryId);
63
- this.options.markDirty();
64
- return true;
65
- }
66
- return false;
67
- }
68
-
69
- handleRepositoryFoldChords(input: Buffer): boolean {
70
- const reduced = reduceRepositoryFoldChordInput({
71
- input,
72
- leftNavSelection: this.options.getLeftNavSelection(),
73
- nowMs: this.options.nowMs(),
74
- prefixAtMs: this.options.getRepositoryToggleChordPrefixAtMs(),
75
- chordTimeoutMs: this.options.chordTimeoutMs,
76
- collapseAllChordPrefix: this.options.collapseAllChordPrefix,
77
- });
78
- this.options.setRepositoryToggleChordPrefixAtMs(reduced.nextPrefixAtMs);
79
- if (reduced.action === 'expand-all') {
80
- this.options.expandAllRepositoryGroups();
81
- this.options.markDirty();
82
- return true;
83
- }
84
- if (reduced.action === 'collapse-all') {
85
- this.options.collapseAllRepositoryGroups();
86
- this.options.markDirty();
87
- return true;
88
- }
89
- return reduced.consumed;
90
- }
91
- }
package/src/ui/surface.ts DELETED
@@ -1,224 +0,0 @@
1
- import { measureDisplayWidth } from '../terminal/snapshot-oracle.ts';
2
-
3
- export type UiColor =
4
- | { kind: 'default' }
5
- | { kind: 'indexed'; index: number }
6
- | { kind: 'rgb'; r: number; g: number; b: number };
7
-
8
- export interface UiStyle {
9
- readonly fg: UiColor;
10
- readonly bg: UiColor;
11
- readonly bold: boolean;
12
- }
13
-
14
- interface UiCell {
15
- glyph: string;
16
- continued: boolean;
17
- style: UiStyle;
18
- }
19
-
20
- export interface UiSurface {
21
- readonly cols: number;
22
- readonly rows: number;
23
- readonly baseStyle: UiStyle;
24
- readonly cells: UiCell[];
25
- }
26
-
27
- const DEFAULT_COLOR: UiColor = {
28
- kind: 'default',
29
- };
30
-
31
- export const DEFAULT_UI_STYLE: UiStyle = {
32
- fg: DEFAULT_COLOR,
33
- bg: DEFAULT_COLOR,
34
- bold: false,
35
- };
36
-
37
- function cloneColor(color: UiColor): UiColor {
38
- if (color.kind === 'default') {
39
- return DEFAULT_COLOR;
40
- }
41
- if (color.kind === 'indexed') {
42
- return {
43
- kind: 'indexed',
44
- index: color.index,
45
- };
46
- }
47
- return {
48
- kind: 'rgb',
49
- r: color.r,
50
- g: color.g,
51
- b: color.b,
52
- };
53
- }
54
-
55
- function cloneStyle(style: UiStyle): UiStyle {
56
- return {
57
- fg: cloneColor(style.fg),
58
- bg: cloneColor(style.bg),
59
- bold: style.bold,
60
- };
61
- }
62
-
63
- function styleEqual(left: UiStyle, right: UiStyle): boolean {
64
- if (left.bold !== right.bold) {
65
- return false;
66
- }
67
- if (left.fg.kind !== right.fg.kind || left.bg.kind !== right.bg.kind) {
68
- return false;
69
- }
70
-
71
- if (left.fg.kind === 'indexed') {
72
- if (left.fg.index !== (right.fg as Extract<UiColor, { kind: 'indexed' }>).index) {
73
- return false;
74
- }
75
- } else if (left.fg.kind === 'rgb') {
76
- const typedRight = right.fg as Extract<UiColor, { kind: 'rgb' }>;
77
- if (left.fg.r !== typedRight.r || left.fg.g !== typedRight.g || left.fg.b !== typedRight.b) {
78
- return false;
79
- }
80
- }
81
-
82
- if (left.bg.kind === 'indexed') {
83
- if (left.bg.index !== (right.bg as Extract<UiColor, { kind: 'indexed' }>).index) {
84
- return false;
85
- }
86
- } else if (left.bg.kind === 'rgb') {
87
- const typedRight = right.bg as Extract<UiColor, { kind: 'rgb' }>;
88
- if (left.bg.r !== typedRight.r || left.bg.g !== typedRight.g || left.bg.b !== typedRight.b) {
89
- return false;
90
- }
91
- }
92
-
93
- return true;
94
- }
95
-
96
- function createCell(style: UiStyle): UiCell {
97
- return {
98
- glyph: ' ',
99
- continued: false,
100
- style: cloneStyle(style),
101
- };
102
- }
103
-
104
- function cellOffset(surface: UiSurface, col: number, row: number): number {
105
- return row * surface.cols + col;
106
- }
107
-
108
- function colorSgrCodes(color: UiColor, target: 'fg' | 'bg'): readonly string[] {
109
- const prefix = target === 'fg' ? '38' : '48';
110
- if (color.kind === 'default') {
111
- return [target === 'fg' ? '39' : '49'];
112
- }
113
- if (color.kind === 'indexed') {
114
- return [prefix, '5', String(color.index)];
115
- }
116
- return [prefix, '2', String(color.r), String(color.g), String(color.b)];
117
- }
118
-
119
- function styleToSgr(style: UiStyle): string {
120
- const codes: string[] = ['0'];
121
- if (style.bold) {
122
- codes.push('1');
123
- }
124
- codes.push(...colorSgrCodes(style.fg, 'fg'));
125
- codes.push(...colorSgrCodes(style.bg, 'bg'));
126
- return `\u001b[${codes.join(';')}m`;
127
- }
128
-
129
- export function createUiSurface(
130
- cols: number,
131
- rows: number,
132
- baseStyle: UiStyle = DEFAULT_UI_STYLE,
133
- ): UiSurface {
134
- const safeCols = Math.max(1, cols);
135
- const safeRows = Math.max(1, rows);
136
- const typedBaseStyle = cloneStyle(baseStyle);
137
- return {
138
- cols: safeCols,
139
- rows: safeRows,
140
- baseStyle: typedBaseStyle,
141
- cells: Array.from({ length: safeCols * safeRows }, () => createCell(typedBaseStyle)),
142
- };
143
- }
144
-
145
- export function fillUiRow(surface: UiSurface, row: number, style: UiStyle): void {
146
- if (row < 0 || row >= surface.rows) {
147
- return;
148
- }
149
- const typedStyle = cloneStyle(style);
150
- for (let col = 0; col < surface.cols; col += 1) {
151
- const cell = surface.cells[cellOffset(surface, col, row)]!;
152
- cell.glyph = ' ';
153
- cell.continued = false;
154
- cell.style = typedStyle;
155
- }
156
- }
157
-
158
- export function drawUiText(
159
- surface: UiSurface,
160
- colStart: number,
161
- row: number,
162
- text: string,
163
- style: UiStyle = surface.baseStyle,
164
- ): void {
165
- if (row < 0 || row >= surface.rows || colStart >= surface.cols) {
166
- return;
167
- }
168
- let col = Math.max(0, colStart);
169
- const typedStyle = cloneStyle(style);
170
-
171
- for (const glyph of text) {
172
- const width = Math.max(0, measureDisplayWidth(glyph));
173
- if (width === 0) {
174
- continue;
175
- }
176
- if (col >= surface.cols) {
177
- break;
178
- }
179
-
180
- if (width === 1) {
181
- const cell = surface.cells[cellOffset(surface, col, row)]!;
182
- cell.glyph = glyph;
183
- cell.continued = false;
184
- cell.style = typedStyle;
185
- col += 1;
186
- continue;
187
- }
188
-
189
- if (col + width > surface.cols) {
190
- break;
191
- }
192
-
193
- const first = surface.cells[cellOffset(surface, col, row)]!;
194
- first.glyph = glyph;
195
- first.continued = false;
196
- first.style = typedStyle;
197
- for (let offset = 1; offset < width && col + offset < surface.cols; offset += 1) {
198
- const cell = surface.cells[cellOffset(surface, col + offset, row)]!;
199
- cell.glyph = '';
200
- cell.continued = true;
201
- cell.style = typedStyle;
202
- }
203
- col += width;
204
- }
205
- }
206
-
207
- export function renderUiSurfaceAnsiRows(surface: UiSurface): readonly string[] {
208
- const rows: string[] = [];
209
- for (let row = 0; row < surface.rows; row += 1) {
210
- let output = '';
211
- let lastStyle: UiStyle | null = null;
212
- for (let col = 0; col < surface.cols; col += 1) {
213
- const cell = surface.cells[cellOffset(surface, col, row)]!;
214
- if (lastStyle === null || !styleEqual(lastStyle, cell.style)) {
215
- output += styleToSgr(cell.style);
216
- lastStyle = cell.style;
217
- }
218
- output += cell.continued ? '' : cell.glyph.length > 0 ? cell.glyph : ' ';
219
- }
220
- output += '\u001b[0m';
221
- rows.push(output);
222
- }
223
- return rows;
224
- }