@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,6 +1,5 @@
1
1
  import type { WorkspaceModel } from '../domain/workspace.ts';
2
2
  import type { TaskPaneAction } from '../mux/harness-core-ui.ts';
3
- import { runTaskPaneAction as runTaskPaneActionFrame } from '../mux/live-mux/actions-task.ts';
4
3
 
5
4
  interface TaskRecordShape {
6
5
  readonly taskId: string;
@@ -16,7 +15,7 @@ interface RuntimeTaskPaneActionService<TTaskRecord extends TaskRecordShape> {
16
15
  taskComplete(taskId: string): Promise<TTaskRecord>;
17
16
  }
18
17
 
19
- interface RuntimeTaskPaneActionsOptions<TTaskRecord extends TaskRecordShape> {
18
+ export interface RuntimeTaskPaneActionsOptions<TTaskRecord extends TaskRecordShape> {
20
19
  readonly workspace: WorkspaceModel;
21
20
  readonly controlPlaneService: RuntimeTaskPaneActionService<TTaskRecord>;
22
21
  readonly repositoriesHas: (repositoryId: string) => boolean;
@@ -41,158 +40,245 @@ interface RuntimeTaskPaneActionsOptions<TTaskRecord extends TaskRecordShape> {
41
40
  readonly openRepositoryPromptForEdit: (repositoryId: string) => void;
42
41
  readonly archiveRepositoryById: (repositoryId: string) => Promise<void>;
43
42
  readonly markDirty: () => void;
44
- readonly runTaskPaneAction?: typeof runTaskPaneActionFrame;
45
43
  }
46
44
 
47
- export class RuntimeTaskPaneActions<TTaskRecord extends TaskRecordShape> {
48
- private readonly runTaskPaneActionFn: typeof runTaskPaneActionFrame;
45
+ export interface RuntimeTaskPaneActions<TTaskRecord extends TaskRecordShape> {
46
+ openTaskCreatePrompt(): void;
47
+ openTaskEditPrompt(taskId: string): void;
48
+ applyTaskRecord(task: TTaskRecord): TTaskRecord;
49
+ applyTaskList(tasks: readonly TTaskRecord[]): boolean;
50
+ queueTaskReorderByIds(orderedActiveTaskIds: readonly string[], label: string): void;
51
+ reorderTaskByDrop(draggedTaskId: string, targetTaskId: string): void;
52
+ runTaskPaneAction(action: TaskPaneAction): void;
53
+ }
54
+
55
+ export function createRuntimeTaskPaneActions<TTaskRecord extends TaskRecordShape>(
56
+ options: RuntimeTaskPaneActionsOptions<TTaskRecord>,
57
+ ): RuntimeTaskPaneActions<TTaskRecord> {
58
+ const applyTaskList = (tasks: readonly TTaskRecord[]): boolean => {
59
+ let changed = false;
60
+ for (const task of tasks) {
61
+ options.setTask(task);
62
+ changed = true;
63
+ }
64
+ if (changed) {
65
+ options.syncTaskPaneSelection();
66
+ options.markDirty();
67
+ }
68
+ return changed;
69
+ };
49
70
 
50
- constructor(private readonly options: RuntimeTaskPaneActionsOptions<TTaskRecord>) {
51
- this.runTaskPaneActionFn = options.runTaskPaneAction ?? runTaskPaneActionFrame;
52
- }
71
+ const applyTaskRecord = (task: TTaskRecord): TTaskRecord => {
72
+ options.setTask(task);
73
+ options.workspace.taskPaneSelectedTaskId = task.taskId;
74
+ if (task.repositoryId !== null && options.repositoriesHas(task.repositoryId)) {
75
+ options.workspace.taskPaneSelectedRepositoryId = task.repositoryId;
76
+ }
77
+ options.workspace.taskPaneSelectionFocus = 'task';
78
+ options.syncTaskPaneSelection();
79
+ options.markDirty();
80
+ return task;
81
+ };
82
+
83
+ const queueTaskReorderByIds = (orderedActiveTaskIds: readonly string[], label: string): void => {
84
+ options.queueControlPlaneOp(async () => {
85
+ const tasks = await options.controlPlaneService.reorderTasks(
86
+ options.taskReorderPayloadIds(orderedActiveTaskIds),
87
+ );
88
+ applyTaskList(tasks);
89
+ }, label);
90
+ };
53
91
 
54
- openTaskCreatePrompt(): void {
55
- const repositoryId = this.options.workspace.taskPaneSelectedRepositoryId;
56
- if (repositoryId === null || !this.options.repositoriesHas(repositoryId)) {
57
- this.options.workspace.taskPaneNotice = 'select a repository first';
58
- this.options.markDirty();
92
+ const openTaskCreatePrompt = (): void => {
93
+ const repositoryId = options.workspace.taskPaneSelectedRepositoryId;
94
+ if (repositoryId === null || !options.repositoriesHas(repositoryId)) {
95
+ options.workspace.taskPaneNotice = 'select a repository first';
96
+ options.markDirty();
59
97
  return;
60
98
  }
61
- this.options.focusDraftComposer();
62
- this.options.workspace.taskPaneNotice = null;
63
- this.options.markDirty();
64
- }
99
+ options.focusDraftComposer();
100
+ options.workspace.taskPaneNotice = null;
101
+ options.markDirty();
102
+ };
65
103
 
66
- openTaskEditPrompt(taskId: string): void {
67
- const task = this.options.getTask(taskId);
104
+ const openTaskEditPrompt = (taskId: string): void => {
105
+ const task = options.getTask(taskId);
68
106
  if (task === undefined) {
69
107
  return;
70
108
  }
71
109
  if (task.repositoryId !== null) {
72
- this.options.workspace.taskPaneSelectedRepositoryId = task.repositoryId;
73
- }
74
- this.options.focusTaskComposer(task.taskId);
75
- this.options.workspace.taskPaneNotice = null;
76
- this.options.markDirty();
77
- }
78
-
79
- applyTaskRecord(task: TTaskRecord): TTaskRecord {
80
- this.options.setTask(task);
81
- this.options.workspace.taskPaneSelectedTaskId = task.taskId;
82
- if (task.repositoryId !== null && this.options.repositoriesHas(task.repositoryId)) {
83
- this.options.workspace.taskPaneSelectedRepositoryId = task.repositoryId;
84
- }
85
- this.options.workspace.taskPaneSelectionFocus = 'task';
86
- this.options.syncTaskPaneSelection();
87
- this.options.markDirty();
88
- return task;
89
- }
110
+ options.workspace.taskPaneSelectedRepositoryId = task.repositoryId;
111
+ }
112
+ options.focusTaskComposer(task.taskId);
113
+ options.workspace.taskPaneNotice = null;
114
+ options.markDirty();
115
+ };
90
116
 
91
- applyTaskList(tasks: readonly TTaskRecord[]): boolean {
92
- let changed = false;
93
- for (const task of tasks) {
94
- this.options.setTask(task);
95
- changed = true;
117
+ const openRepositoryPromptForEditFromSelection = (): void => {
118
+ const selectedRepositoryId = options.workspace.taskPaneSelectedRepositoryId;
119
+ if (selectedRepositoryId === null || !options.repositoriesHas(selectedRepositoryId)) {
120
+ options.workspace.taskPaneNotice = 'select a repository first';
121
+ options.markDirty();
122
+ return;
96
123
  }
97
- if (changed) {
98
- this.options.syncTaskPaneSelection();
99
- this.options.markDirty();
124
+ options.workspace.taskPaneSelectionFocus = 'repository';
125
+ options.workspace.taskPaneNotice = null;
126
+ options.openRepositoryPromptForEdit(selectedRepositoryId);
127
+ };
128
+
129
+ const queueArchiveRepositoryFromSelection = (): void => {
130
+ const selectedRepositoryId = options.workspace.taskPaneSelectedRepositoryId;
131
+ if (selectedRepositoryId === null || !options.repositoriesHas(selectedRepositoryId)) {
132
+ options.workspace.taskPaneNotice = 'select a repository first';
133
+ options.markDirty();
134
+ return;
100
135
  }
101
- return changed;
102
- }
136
+ options.workspace.taskPaneSelectionFocus = 'repository';
137
+ options.queueControlPlaneOp(async () => {
138
+ await options.archiveRepositoryById(selectedRepositoryId);
139
+ options.syncTaskPaneRepositorySelection();
140
+ }, 'tasks-archive-repository');
141
+ };
103
142
 
104
- queueTaskReorderByIds(orderedActiveTaskIds: readonly string[], label: string): void {
105
- this.options.queueControlPlaneOp(async () => {
106
- const tasks = await this.options.controlPlaneService.reorderTasks(
107
- this.options.taskReorderPayloadIds(orderedActiveTaskIds),
108
- );
109
- this.applyTaskList(tasks);
110
- }, label);
111
- }
143
+ const requireSelectedTaskOrNotice = (): TTaskRecord | null => {
144
+ const selectedTask = options.selectedTask();
145
+ if (selectedTask !== null) {
146
+ return selectedTask;
147
+ }
148
+ options.workspace.taskPaneNotice = 'select a task first';
149
+ options.markDirty();
150
+ return null;
151
+ };
152
+
153
+ const queueDeleteTask = (taskId: string): void => {
154
+ options.queueControlPlaneOp(async () => {
155
+ options.clearTaskAutosaveTimer(taskId);
156
+ await options.controlPlaneService.deleteTask(taskId);
157
+ options.deleteTask(taskId);
158
+ options.deleteTaskComposer(taskId);
159
+ if (
160
+ options.workspace.taskEditorTarget.kind === 'task' &&
161
+ options.workspace.taskEditorTarget.taskId === taskId
162
+ ) {
163
+ options.workspace.taskEditorTarget = {
164
+ kind: 'draft',
165
+ };
166
+ }
167
+ options.syncTaskPaneSelection();
168
+ options.markDirty();
169
+ }, 'tasks-delete');
170
+ };
112
171
 
113
- reorderTaskByDrop(draggedTaskId: string, targetTaskId: string): void {
114
- const reordered = this.options.reorderedActiveTaskIdsForDrop(draggedTaskId, targetTaskId);
172
+ const queueTaskStatusUpdate = (taskId: string, status: 'ready' | 'draft' | 'complete'): void => {
173
+ const labelByStatus: Readonly<Record<typeof status, string>> = {
174
+ ready: 'tasks-ready',
175
+ draft: 'tasks-draft',
176
+ complete: 'tasks-complete',
177
+ };
178
+ options.queueControlPlaneOp(async () => {
179
+ const task =
180
+ status === 'ready'
181
+ ? await options.controlPlaneService.taskReady(taskId)
182
+ : status === 'draft'
183
+ ? await options.controlPlaneService.taskDraft(taskId)
184
+ : await options.controlPlaneService.taskComplete(taskId);
185
+ applyTaskRecord(task);
186
+ }, labelByStatus[status]);
187
+ };
188
+
189
+ const reorderSelectedTask = (selectedTaskId: string, direction: 'up' | 'down'): void => {
190
+ const activeTasks = options.orderedTaskRecords().filter((task) => task.status !== 'completed');
191
+ const selectedIndex = activeTasks.findIndex((task) => task.taskId === selectedTaskId);
192
+ if (selectedIndex < 0) {
193
+ options.workspace.taskPaneNotice = 'cannot reorder completed tasks';
194
+ options.markDirty();
195
+ return;
196
+ }
197
+ const swapIndex = direction === 'up' ? selectedIndex - 1 : selectedIndex + 1;
198
+ if (swapIndex < 0 || swapIndex >= activeTasks.length) {
199
+ return;
200
+ }
201
+ const reordered = [...activeTasks];
202
+ const currentTask = reordered[selectedIndex]!;
203
+ reordered[selectedIndex] = reordered[swapIndex]!;
204
+ reordered[swapIndex] = currentTask;
205
+ options.workspace.taskPaneSelectionFocus = 'task';
206
+ queueTaskReorderByIds(
207
+ reordered.map((task) => task.taskId),
208
+ direction === 'up' ? 'tasks-reorder-up' : 'tasks-reorder-down',
209
+ );
210
+ };
211
+
212
+ const reorderTaskByDrop = (draggedTaskId: string, targetTaskId: string): void => {
213
+ const reordered = options.reorderedActiveTaskIdsForDrop(draggedTaskId, targetTaskId);
115
214
  if (reordered === 'cannot-reorder-completed') {
116
- this.options.workspace.taskPaneNotice = 'cannot reorder completed tasks';
117
- this.options.markDirty();
215
+ options.workspace.taskPaneNotice = 'cannot reorder completed tasks';
216
+ options.markDirty();
118
217
  return;
119
218
  }
120
219
  if (reordered === null) {
121
220
  return;
122
221
  }
123
- this.queueTaskReorderByIds(reordered, 'tasks-reorder-drag');
124
- }
125
-
126
- runTaskPaneAction(action: TaskPaneAction): void {
127
- this.runTaskPaneActionFn({
128
- action,
129
- openTaskCreatePrompt: () => {
130
- this.openTaskCreatePrompt();
131
- },
132
- openRepositoryPromptForCreate: () => {
133
- this.options.openRepositoryPromptForCreate();
134
- },
135
- selectedRepositoryId: this.options.workspace.taskPaneSelectedRepositoryId,
136
- repositoryExists: (repositoryId) => this.options.repositoriesHas(repositoryId),
137
- setTaskPaneNotice: (notice) => {
138
- this.options.workspace.taskPaneNotice = notice;
139
- },
140
- markDirty: () => {
141
- this.options.markDirty();
142
- },
143
- setTaskPaneSelectionFocus: (focus) => {
144
- this.options.workspace.taskPaneSelectionFocus = focus;
145
- },
146
- openRepositoryPromptForEdit: (repositoryId) => {
147
- this.options.openRepositoryPromptForEdit(repositoryId);
148
- },
149
- queueArchiveRepository: (repositoryId) => {
150
- this.options.queueControlPlaneOp(async () => {
151
- await this.options.archiveRepositoryById(repositoryId);
152
- this.options.syncTaskPaneRepositorySelection();
153
- }, 'tasks-archive-repository');
154
- },
155
- selectedTask: this.options.selectedTask(),
156
- openTaskEditPrompt: (taskId) => {
157
- this.openTaskEditPrompt(taskId);
158
- },
159
- queueDeleteTask: (taskId) => {
160
- this.options.queueControlPlaneOp(async () => {
161
- this.options.clearTaskAutosaveTimer(taskId);
162
- await this.options.controlPlaneService.deleteTask(taskId);
163
- this.options.deleteTask(taskId);
164
- this.options.deleteTaskComposer(taskId);
165
- if (
166
- this.options.workspace.taskEditorTarget.kind === 'task' &&
167
- this.options.workspace.taskEditorTarget.taskId === taskId
168
- ) {
169
- this.options.workspace.taskEditorTarget = {
170
- kind: 'draft',
171
- };
172
- }
173
- this.options.syncTaskPaneSelection();
174
- this.options.markDirty();
175
- }, 'tasks-delete');
176
- },
177
- queueTaskReady: (taskId) => {
178
- this.options.queueControlPlaneOp(async () => {
179
- this.applyTaskRecord(await this.options.controlPlaneService.taskReady(taskId));
180
- }, 'tasks-ready');
181
- },
182
- queueTaskDraft: (taskId) => {
183
- this.options.queueControlPlaneOp(async () => {
184
- this.applyTaskRecord(await this.options.controlPlaneService.taskDraft(taskId));
185
- }, 'tasks-draft');
186
- },
187
- queueTaskComplete: (taskId) => {
188
- this.options.queueControlPlaneOp(async () => {
189
- this.applyTaskRecord(await this.options.controlPlaneService.taskComplete(taskId));
190
- }, 'tasks-complete');
191
- },
192
- orderedTaskRecords: () => this.options.orderedTaskRecords(),
193
- queueTaskReorderByIds: (orderedTaskIds, label) => {
194
- this.queueTaskReorderByIds(orderedTaskIds, label);
195
- },
196
- });
197
- }
222
+ queueTaskReorderByIds(reordered, 'tasks-reorder-drag');
223
+ };
224
+
225
+ const runTaskPaneAction = (action: TaskPaneAction): void => {
226
+ if (action === 'task.create') {
227
+ openTaskCreatePrompt();
228
+ return;
229
+ }
230
+ if (action === 'repository.create') {
231
+ options.workspace.taskPaneNotice = null;
232
+ options.openRepositoryPromptForCreate();
233
+ return;
234
+ }
235
+ if (action === 'repository.edit') {
236
+ openRepositoryPromptForEditFromSelection();
237
+ return;
238
+ }
239
+ if (action === 'repository.archive') {
240
+ queueArchiveRepositoryFromSelection();
241
+ return;
242
+ }
243
+ const selectedTask = requireSelectedTaskOrNotice();
244
+ if (selectedTask === null) {
245
+ return;
246
+ }
247
+ if (action === 'task.edit') {
248
+ options.workspace.taskPaneSelectionFocus = 'task';
249
+ openTaskEditPrompt(selectedTask.taskId);
250
+ return;
251
+ }
252
+ if (action === 'task.delete') {
253
+ options.workspace.taskPaneSelectionFocus = 'task';
254
+ queueDeleteTask(selectedTask.taskId);
255
+ return;
256
+ }
257
+ if (action === 'task.ready') {
258
+ options.workspace.taskPaneSelectionFocus = 'task';
259
+ queueTaskStatusUpdate(selectedTask.taskId, 'ready');
260
+ return;
261
+ }
262
+ if (action === 'task.draft') {
263
+ options.workspace.taskPaneSelectionFocus = 'task';
264
+ queueTaskStatusUpdate(selectedTask.taskId, 'draft');
265
+ return;
266
+ }
267
+ if (action === 'task.complete') {
268
+ options.workspace.taskPaneSelectionFocus = 'task';
269
+ queueTaskStatusUpdate(selectedTask.taskId, 'complete');
270
+ return;
271
+ }
272
+ reorderSelectedTask(selectedTask.taskId, action === 'task.reorder-up' ? 'up' : 'down');
273
+ };
274
+
275
+ return {
276
+ openTaskCreatePrompt,
277
+ openTaskEditPrompt,
278
+ applyTaskRecord,
279
+ applyTaskList,
280
+ queueTaskReorderByIds,
281
+ reorderTaskByDrop,
282
+ runTaskPaneAction,
283
+ };
198
284
  }