dmux 2.2.0 → 3.0.0

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 (197) hide show
  1. package/dist/DmuxApp.d.ts.map +1 -1
  2. package/dist/DmuxApp.js +412 -179
  3. package/dist/DmuxApp.js.map +1 -1
  4. package/dist/MergePane.d.ts.map +1 -1
  5. package/dist/MergePane.js +4 -15
  6. package/dist/MergePane.js.map +1 -1
  7. package/dist/PaneAnalyzer.d.ts +45 -0
  8. package/dist/PaneAnalyzer.d.ts.map +1 -0
  9. package/dist/PaneAnalyzer.js +278 -0
  10. package/dist/PaneAnalyzer.js.map +1 -0
  11. package/dist/_plugin-vue_export-helper.css +1 -0
  12. package/dist/actions/index.d.ts +19 -0
  13. package/dist/actions/index.d.ts.map +1 -0
  14. package/dist/actions/index.js +54 -0
  15. package/dist/actions/index.js.map +1 -0
  16. package/dist/actions/paneActions.d.ts +45 -0
  17. package/dist/actions/paneActions.d.ts.map +1 -0
  18. package/dist/actions/paneActions.js +932 -0
  19. package/dist/actions/paneActions.js.map +1 -0
  20. package/dist/actions/types.d.ts +101 -0
  21. package/dist/actions/types.d.ts.map +1 -0
  22. package/dist/actions/types.js +129 -0
  23. package/dist/actions/types.js.map +1 -0
  24. package/dist/adapters/apiActionHandler.d.ts +64 -0
  25. package/dist/adapters/apiActionHandler.d.ts.map +1 -0
  26. package/dist/adapters/apiActionHandler.js +170 -0
  27. package/dist/adapters/apiActionHandler.js.map +1 -0
  28. package/dist/adapters/tuiActionHandler.d.ts +57 -0
  29. package/dist/adapters/tuiActionHandler.d.ts.map +1 -0
  30. package/dist/adapters/tuiActionHandler.js +152 -0
  31. package/dist/adapters/tuiActionHandler.js.map +1 -0
  32. package/dist/chunks/_plugin-vue_export-helper-Cvoq67hi.js +28 -0
  33. package/dist/components/ActionChoiceDialog.d.ts +16 -0
  34. package/dist/components/ActionChoiceDialog.d.ts.map +1 -0
  35. package/dist/components/ActionChoiceDialog.js +29 -0
  36. package/dist/components/ActionChoiceDialog.js.map +1 -0
  37. package/dist/components/ActionConfirmDialog.d.ts +16 -0
  38. package/dist/components/ActionConfirmDialog.d.ts.map +1 -0
  39. package/dist/components/ActionConfirmDialog.js +31 -0
  40. package/dist/components/ActionConfirmDialog.js.map +1 -0
  41. package/dist/components/ActionInputDialog.d.ts +16 -0
  42. package/dist/components/ActionInputDialog.d.ts.map +1 -0
  43. package/dist/components/ActionInputDialog.js +49 -0
  44. package/dist/components/ActionInputDialog.js.map +1 -0
  45. package/dist/components/ActionProgressDialog.d.ts +13 -0
  46. package/dist/components/ActionProgressDialog.d.ts.map +1 -0
  47. package/dist/components/ActionProgressDialog.js +20 -0
  48. package/dist/components/ActionProgressDialog.js.map +1 -0
  49. package/dist/components/FooterHelp.d.ts +2 -0
  50. package/dist/components/FooterHelp.d.ts.map +1 -1
  51. package/dist/components/FooterHelp.js +9 -2
  52. package/dist/components/FooterHelp.js.map +1 -1
  53. package/dist/components/KebabMenu.d.ts +10 -0
  54. package/dist/components/KebabMenu.d.ts.map +1 -0
  55. package/dist/components/KebabMenu.js +18 -0
  56. package/dist/components/KebabMenu.js.map +1 -0
  57. package/dist/components/LoadingIndicator.d.ts.map +1 -1
  58. package/dist/components/LoadingIndicator.js +5 -5
  59. package/dist/components/LoadingIndicator.js.map +1 -1
  60. package/dist/components/PaneCard.d.ts +1 -0
  61. package/dist/components/PaneCard.d.ts.map +1 -1
  62. package/dist/components/PaneCard.js +21 -20
  63. package/dist/components/PaneCard.js.map +1 -1
  64. package/dist/components/PanesGrid.d.ts +1 -0
  65. package/dist/components/PanesGrid.d.ts.map +1 -1
  66. package/dist/components/PanesGrid.js +5 -4
  67. package/dist/components/PanesGrid.js.map +1 -1
  68. package/dist/components/QRCode.d.ts +7 -0
  69. package/dist/components/QRCode.d.ts.map +1 -0
  70. package/dist/components/QRCode.js +19 -0
  71. package/dist/components/QRCode.js.map +1 -0
  72. package/dist/components/Spinner.d.ts +10 -0
  73. package/dist/components/Spinner.d.ts.map +1 -0
  74. package/dist/components/Spinner.js +15 -0
  75. package/dist/components/Spinner.js.map +1 -0
  76. package/dist/dashboard.html +14 -0
  77. package/dist/dashboard.js +2 -0
  78. package/dist/hooks/useActionSystem.d.ts +31 -0
  79. package/dist/hooks/useActionSystem.d.ts.map +1 -0
  80. package/dist/hooks/useActionSystem.js +95 -0
  81. package/dist/hooks/useActionSystem.js.map +1 -0
  82. package/dist/hooks/useAgentStatus.d.ts +4 -3
  83. package/dist/hooks/useAgentStatus.d.ts.map +1 -1
  84. package/dist/hooks/useAgentStatus.js +45 -194
  85. package/dist/hooks/useAgentStatus.js.map +1 -1
  86. package/dist/hooks/usePaneCreation.d.ts +2 -1
  87. package/dist/hooks/usePaneCreation.d.ts.map +1 -1
  88. package/dist/hooks/usePaneCreation.js +46 -100
  89. package/dist/hooks/usePaneCreation.js.map +1 -1
  90. package/dist/hooks/usePanes.d.ts.map +1 -1
  91. package/dist/hooks/usePanes.js +5 -3
  92. package/dist/hooks/usePanes.js.map +1 -1
  93. package/dist/hooks/useTerminalWidth.d.ts.map +1 -1
  94. package/dist/hooks/useTerminalWidth.js +18 -1
  95. package/dist/hooks/useTerminalWidth.js.map +1 -1
  96. package/dist/hooks/useWorktreeActions.d.ts.map +1 -1
  97. package/dist/hooks/useWorktreeActions.js +4 -0
  98. package/dist/hooks/useWorktreeActions.js.map +1 -1
  99. package/dist/index.js +43 -6
  100. package/dist/index.js.map +1 -1
  101. package/dist/server/actionsApi.d.ts +37 -0
  102. package/dist/server/actionsApi.d.ts.map +1 -0
  103. package/dist/server/actionsApi.js +256 -0
  104. package/dist/server/actionsApi.js.map +1 -0
  105. package/dist/server/embedded-assets.d.ts +13 -0
  106. package/dist/server/embedded-assets.d.ts.map +1 -0
  107. package/dist/server/embedded-assets.js +5012 -0
  108. package/dist/server/embedded-assets.js.map +1 -0
  109. package/dist/server/index.d.ts +21 -0
  110. package/dist/server/index.d.ts.map +1 -0
  111. package/dist/server/index.js +99 -0
  112. package/dist/server/index.js.map +1 -0
  113. package/dist/server/routes.d.ts +3 -0
  114. package/dist/server/routes.d.ts.map +1 -0
  115. package/dist/server/routes.js +672 -0
  116. package/dist/server/routes.js.map +1 -0
  117. package/dist/server/static.d.ts +6 -0
  118. package/dist/server/static.d.ts.map +1 -0
  119. package/dist/server/static.js +3040 -0
  120. package/dist/server/static.js.map +1 -0
  121. package/dist/services/ConfigWatcher.d.ts +20 -0
  122. package/dist/services/ConfigWatcher.d.ts.map +1 -0
  123. package/dist/services/ConfigWatcher.js +75 -0
  124. package/dist/services/ConfigWatcher.js.map +1 -0
  125. package/dist/services/PaneWorkerManager.d.ts +69 -0
  126. package/dist/services/PaneWorkerManager.d.ts.map +1 -0
  127. package/dist/services/PaneWorkerManager.js +272 -0
  128. package/dist/services/PaneWorkerManager.js.map +1 -0
  129. package/dist/services/StatusDetector.d.ts +87 -0
  130. package/dist/services/StatusDetector.d.ts.map +1 -0
  131. package/dist/services/StatusDetector.js +387 -0
  132. package/dist/services/StatusDetector.js.map +1 -0
  133. package/dist/services/TerminalDiffer.d.ts +85 -0
  134. package/dist/services/TerminalDiffer.d.ts.map +1 -0
  135. package/dist/services/TerminalDiffer.js +499 -0
  136. package/dist/services/TerminalDiffer.js.map +1 -0
  137. package/dist/services/TerminalStreamer.d.ts +80 -0
  138. package/dist/services/TerminalStreamer.d.ts.map +1 -0
  139. package/dist/services/TerminalStreamer.js +490 -0
  140. package/dist/services/TerminalStreamer.js.map +1 -0
  141. package/dist/services/TunnelService.d.ts +9 -0
  142. package/dist/services/TunnelService.d.ts.map +1 -0
  143. package/dist/services/TunnelService.js +34 -0
  144. package/dist/services/TunnelService.js.map +1 -0
  145. package/dist/services/WorkerMessageBus.d.ts +48 -0
  146. package/dist/services/WorkerMessageBus.d.ts.map +1 -0
  147. package/dist/services/WorkerMessageBus.js +120 -0
  148. package/dist/services/WorkerMessageBus.js.map +1 -0
  149. package/dist/shared/StateManager.d.ts +34 -0
  150. package/dist/shared/StateManager.d.ts.map +1 -0
  151. package/dist/shared/StateManager.js +108 -0
  152. package/dist/shared/StateManager.js.map +1 -0
  153. package/dist/shared/StreamProtocol.d.ts +75 -0
  154. package/dist/shared/StreamProtocol.d.ts.map +1 -0
  155. package/dist/shared/StreamProtocol.js +37 -0
  156. package/dist/shared/StreamProtocol.js.map +1 -0
  157. package/dist/terminal.html +17 -0
  158. package/dist/terminal.js +3 -0
  159. package/dist/types.d.ts +21 -1
  160. package/dist/types.d.ts.map +1 -1
  161. package/dist/utils/agentDetection.d.ts +18 -0
  162. package/dist/utils/agentDetection.d.ts.map +1 -0
  163. package/dist/utils/agentDetection.js +73 -0
  164. package/dist/utils/agentDetection.js.map +1 -0
  165. package/dist/utils/aiMerge.d.ts +35 -0
  166. package/dist/utils/aiMerge.d.ts.map +1 -0
  167. package/dist/utils/aiMerge.js +298 -0
  168. package/dist/utils/aiMerge.js.map +1 -0
  169. package/dist/utils/conflictResolutionPane.d.ts +19 -0
  170. package/dist/utils/conflictResolutionPane.d.ts.map +1 -0
  171. package/dist/utils/conflictResolutionPane.js +214 -0
  172. package/dist/utils/conflictResolutionPane.js.map +1 -0
  173. package/dist/utils/mergeExecution.d.ts +52 -0
  174. package/dist/utils/mergeExecution.d.ts.map +1 -0
  175. package/dist/utils/mergeExecution.js +192 -0
  176. package/dist/utils/mergeExecution.js.map +1 -0
  177. package/dist/utils/mergeValidation.d.ts +67 -0
  178. package/dist/utils/mergeValidation.d.ts.map +1 -0
  179. package/dist/utils/mergeValidation.js +213 -0
  180. package/dist/utils/mergeValidation.js.map +1 -0
  181. package/dist/utils/paneCreation.d.ts +17 -0
  182. package/dist/utils/paneCreation.d.ts.map +1 -0
  183. package/dist/utils/paneCreation.js +274 -0
  184. package/dist/utils/paneCreation.js.map +1 -0
  185. package/dist/utils/port.d.ts +5 -0
  186. package/dist/utils/port.d.ts.map +1 -0
  187. package/dist/utils/port.js +54 -0
  188. package/dist/utils/port.js.map +1 -0
  189. package/dist/workers/PaneWorker.d.ts +2 -0
  190. package/dist/workers/PaneWorker.d.ts.map +1 -0
  191. package/dist/workers/PaneWorker.js +362 -0
  192. package/dist/workers/PaneWorker.js.map +1 -0
  193. package/dist/workers/WorkerMessages.d.ts +36 -0
  194. package/dist/workers/WorkerMessages.d.ts.map +1 -0
  195. package/dist/workers/WorkerMessages.js +9 -0
  196. package/dist/workers/WorkerMessages.js.map +1 -0
  197. package/package.json +19 -5
@@ -0,0 +1,932 @@
1
+ /**
2
+ * Standardized Pane Actions
3
+ *
4
+ * Core action implementations that work across all interfaces.
5
+ * Each action returns a standardized ActionResult that interfaces can handle.
6
+ */
7
+ import { execSync } from 'child_process';
8
+ /**
9
+ * Generate commit message with timeout and error handling
10
+ * Returns null if it fails, so caller can fall back to manual input
11
+ */
12
+ async function generateCommitMessageSafe(repoPath, timeoutMs = 15000) {
13
+ try {
14
+ const { generateCommitMessage } = await import('../utils/aiMerge.js');
15
+ // Race between generation and timeout
16
+ const result = await Promise.race([
17
+ generateCommitMessage(repoPath),
18
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeoutMs)),
19
+ ]);
20
+ if (!result) {
21
+ console.error('[generateCommitMessageSafe] AI generation returned null');
22
+ }
23
+ return result;
24
+ }
25
+ catch (error) {
26
+ console.error('[generateCommitMessageSafe] Error or timeout:', error);
27
+ return null;
28
+ }
29
+ }
30
+ /**
31
+ * View/Jump to a pane
32
+ */
33
+ export async function viewPane(pane, context) {
34
+ try {
35
+ execSync(`tmux select-pane -t '${pane.paneId}'`, { stdio: 'pipe' });
36
+ return {
37
+ type: 'navigation',
38
+ message: `Jumped to pane: ${pane.slug}`,
39
+ targetPaneId: pane.id,
40
+ dismissable: true,
41
+ };
42
+ }
43
+ catch (error) {
44
+ return {
45
+ type: 'error',
46
+ message: 'Failed to jump to pane - it may have been closed',
47
+ dismissable: true,
48
+ };
49
+ }
50
+ }
51
+ /**
52
+ * Close a pane - presents options for how to close
53
+ */
54
+ export async function closePane(pane, context) {
55
+ const options = [
56
+ {
57
+ id: 'kill_only',
58
+ label: 'Just close pane',
59
+ description: 'Keep worktree and branch',
60
+ default: true,
61
+ },
62
+ ];
63
+ if (pane.worktreePath) {
64
+ options.push({
65
+ id: 'kill_and_clean',
66
+ label: 'Close and remove worktree',
67
+ description: 'Delete worktree but keep branch',
68
+ danger: true,
69
+ }, {
70
+ id: 'kill_clean_branch',
71
+ label: 'Close and delete everything',
72
+ description: 'Remove worktree and delete branch',
73
+ danger: true,
74
+ });
75
+ }
76
+ return {
77
+ type: 'choice',
78
+ title: 'Close Pane',
79
+ message: `How do you want to close "${pane.slug}"?`,
80
+ options,
81
+ onSelect: async (optionId) => {
82
+ return executeCloseOption(pane, context, optionId);
83
+ },
84
+ dismissable: true,
85
+ };
86
+ }
87
+ /**
88
+ * Execute the selected close option
89
+ */
90
+ async function executeCloseOption(pane, context, option) {
91
+ try {
92
+ // Kill the tmux pane
93
+ try {
94
+ execSync(`tmux kill-pane -t '${pane.paneId}'`, { stdio: 'pipe' });
95
+ }
96
+ catch {
97
+ // Pane might already be dead
98
+ }
99
+ // Handle worktree cleanup based on option
100
+ if (pane.worktreePath && (option === 'kill_and_clean' || option === 'kill_clean_branch')) {
101
+ const mainRepoPath = pane.worktreePath.replace(/\/\.dmux\/worktrees\/[^/]+$/, '');
102
+ try {
103
+ execSync(`git worktree remove "${pane.worktreePath}" --force`, {
104
+ stdio: 'pipe',
105
+ cwd: mainRepoPath,
106
+ });
107
+ }
108
+ catch {
109
+ // Worktree might already be removed
110
+ }
111
+ // Delete branch if requested
112
+ if (option === 'kill_clean_branch') {
113
+ try {
114
+ execSync(`git branch -D ${pane.slug}`, {
115
+ stdio: 'pipe',
116
+ cwd: mainRepoPath,
117
+ });
118
+ }
119
+ catch {
120
+ // Branch might not exist or already deleted
121
+ }
122
+ }
123
+ }
124
+ // Remove from panes list
125
+ const updatedPanes = context.panes.filter(p => p.id !== pane.id);
126
+ await context.savePanes(updatedPanes);
127
+ if (context.onPaneRemove) {
128
+ context.onPaneRemove(pane.id);
129
+ }
130
+ return {
131
+ type: 'success',
132
+ message: `Pane "${pane.slug}" closed successfully`,
133
+ dismissable: true,
134
+ };
135
+ }
136
+ catch (error) {
137
+ return {
138
+ type: 'error',
139
+ message: `Failed to close pane: ${error}`,
140
+ dismissable: true,
141
+ };
142
+ }
143
+ }
144
+ /**
145
+ * Merge a worktree into the main branch with comprehensive pre-checks
146
+ */
147
+ export async function mergePane(pane, context, params) {
148
+ if (!pane.worktreePath) {
149
+ return {
150
+ type: 'error',
151
+ message: 'This pane has no worktree to merge',
152
+ dismissable: true,
153
+ };
154
+ }
155
+ // Import merge utilities dynamically
156
+ const { validateMerge } = await import('../utils/mergeValidation.js');
157
+ const mainRepoPath = pane.worktreePath.replace(/\/\.dmux\/worktrees\/[^/]+$/, '');
158
+ // Run pre-merge validation
159
+ const validation = validateMerge(mainRepoPath, pane.worktreePath, pane.slug);
160
+ // If there are issues, present them to the user
161
+ if (!validation.canMerge) {
162
+ return handleMergeIssues(pane, context, validation, mainRepoPath);
163
+ }
164
+ // No issues detected, proceed with merge confirmation
165
+ return {
166
+ type: 'confirm',
167
+ title: 'Merge Worktree',
168
+ message: `Merge "${pane.slug}" into ${validation.mainBranch}?`,
169
+ confirmLabel: 'Merge',
170
+ cancelLabel: 'Cancel',
171
+ onConfirm: async () => {
172
+ return executeMerge(pane, context, validation.mainBranch, mainRepoPath);
173
+ },
174
+ onCancel: async () => {
175
+ return {
176
+ type: 'info',
177
+ message: 'Merge cancelled',
178
+ dismissable: true,
179
+ };
180
+ },
181
+ };
182
+ }
183
+ /**
184
+ * Handle detected merge issues
185
+ */
186
+ async function handleMergeIssues(pane, context, validation, mainRepoPath) {
187
+ const { issues, mainBranch } = validation;
188
+ const { commitChanges, stashChanges } = await import('../utils/mergeValidation.js');
189
+ const { generateCommitMessage } = await import('../utils/aiMerge.js');
190
+ // Group issues by type for clearer handling
191
+ const mainDirty = issues.find((i) => i.type === 'main_dirty');
192
+ const worktreeUncommitted = issues.find((i) => i.type === 'worktree_uncommitted');
193
+ const mergeConflict = issues.find((i) => i.type === 'merge_conflict');
194
+ const nothingToMerge = issues.find((i) => i.type === 'nothing_to_merge');
195
+ // Handle "nothing to merge" case
196
+ if (nothingToMerge) {
197
+ return {
198
+ type: 'info',
199
+ message: 'No new commits to merge',
200
+ dismissable: true,
201
+ };
202
+ }
203
+ // Handle main branch dirty
204
+ if (mainDirty) {
205
+ return {
206
+ type: 'choice',
207
+ title: 'Main Branch Has Uncommitted Changes',
208
+ message: `${mainBranch} has uncommitted changes in:\n${mainDirty.files.slice(0, 5).join('\n')}${mainDirty.files.length > 5 ? '\n...' : ''}`,
209
+ options: [
210
+ {
211
+ id: 'commit_automatic',
212
+ label: 'AI commit (automatic)',
213
+ description: 'Auto-generate and commit immediately',
214
+ default: true,
215
+ },
216
+ {
217
+ id: 'commit_ai_editable',
218
+ label: 'AI commit (editable)',
219
+ description: 'Generate message from diff, edit before commit',
220
+ },
221
+ {
222
+ id: 'commit_manual',
223
+ label: 'Manual commit message',
224
+ description: 'Write your own commit message',
225
+ },
226
+ {
227
+ id: 'stash_main',
228
+ label: 'Stash changes in main',
229
+ description: 'Temporarily stash uncommitted changes',
230
+ },
231
+ {
232
+ id: 'cancel',
233
+ label: 'Cancel merge',
234
+ description: 'Resolve manually later',
235
+ },
236
+ ],
237
+ onSelect: async (optionId) => {
238
+ if (optionId === 'cancel') {
239
+ return { type: 'info', message: 'Merge cancelled', dismissable: true };
240
+ }
241
+ if (optionId === 'stash_main') {
242
+ const result = stashChanges(mainRepoPath);
243
+ if (!result.success) {
244
+ return { type: 'error', message: `Stash failed: ${result.error}`, dismissable: true };
245
+ }
246
+ // Retry merge after stashing
247
+ return mergePane(pane, context, { mainBranch });
248
+ }
249
+ if (optionId === 'commit_ai_editable') {
250
+ try {
251
+ const { stageAllChanges } = await import('../utils/mergeValidation.js');
252
+ const { getComprehensiveDiff } = await import('../utils/aiMerge.js');
253
+ // Stage all changes first
254
+ const stageResult = stageAllChanges(mainRepoPath);
255
+ if (!stageResult.success) {
256
+ return { type: 'error', message: `Failed to stage changes: ${stageResult.error}`, dismissable: true };
257
+ }
258
+ // Get diff and generate message
259
+ const { diff, summary } = getComprehensiveDiff(mainRepoPath);
260
+ console.error('[commit_ai_editable] Calling generateCommitMessageSafe for main repo');
261
+ const generatedMessage = await generateCommitMessageSafe(mainRepoPath);
262
+ console.error('[commit_ai_editable] Generated message:', generatedMessage);
263
+ // If AI generation failed, fall back to manual with explanation
264
+ if (!generatedMessage) {
265
+ console.error('[commit_ai_editable] Falling back to manual input');
266
+ return {
267
+ type: 'input',
268
+ title: 'Enter Commit Message',
269
+ message: `⚠️ Auto-generation failed or timed out. Please write a commit message manually.\n\nFiles changed:\n${summary}`,
270
+ placeholder: 'feat: add new feature',
271
+ onSubmit: async (message) => {
272
+ if (!message || !message.trim()) {
273
+ return { type: 'error', message: 'Commit message cannot be empty', dismissable: true };
274
+ }
275
+ const result = commitChanges(mainRepoPath, message.trim());
276
+ if (!result.success) {
277
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
278
+ }
279
+ // Retry merge after committing
280
+ return mergePane(pane, context, { mainBranch });
281
+ },
282
+ dismissable: true,
283
+ };
284
+ }
285
+ console.error('[commit_ai_editable] Returning editable input with generated message');
286
+ return {
287
+ type: 'input',
288
+ title: 'Review & Edit Commit Message',
289
+ message: `Files changed:\n${summary}\n\nGenerated message (edit as needed):`,
290
+ placeholder: 'feat: add new feature',
291
+ defaultValue: generatedMessage,
292
+ onSubmit: async (message) => {
293
+ if (!message || !message.trim()) {
294
+ return { type: 'error', message: 'Commit message cannot be empty', dismissable: true };
295
+ }
296
+ const result = commitChanges(mainRepoPath, message.trim());
297
+ if (!result.success) {
298
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
299
+ }
300
+ // Retry merge after committing
301
+ return mergePane(pane, context, { mainBranch });
302
+ },
303
+ dismissable: true,
304
+ };
305
+ }
306
+ catch (error) {
307
+ console.error('[commit_ai_editable] Unexpected error:', error);
308
+ return {
309
+ type: 'error',
310
+ message: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
311
+ dismissable: true,
312
+ };
313
+ }
314
+ }
315
+ if (optionId === 'commit_automatic') {
316
+ const { stageAllChanges } = await import('../utils/mergeValidation.js');
317
+ const { getComprehensiveDiff } = await import('../utils/aiMerge.js');
318
+ // Stage all changes first
319
+ const stageResult = stageAllChanges(mainRepoPath);
320
+ if (!stageResult.success) {
321
+ return { type: 'error', message: `Failed to stage changes: ${stageResult.error}`, dismissable: true };
322
+ }
323
+ // Generate commit message
324
+ const message = await generateCommitMessageSafe(mainRepoPath);
325
+ // If AI generation failed, fall back to manual input
326
+ if (!message) {
327
+ const { summary } = getComprehensiveDiff(mainRepoPath);
328
+ return {
329
+ type: 'input',
330
+ title: 'Enter Commit Message',
331
+ message: `⚠️ Auto-generation failed or timed out. Please write a commit message manually.\n\nFiles changed:\n${summary}`,
332
+ placeholder: 'feat: add new feature',
333
+ onSubmit: async (manualMessage) => {
334
+ if (!manualMessage || !manualMessage.trim()) {
335
+ return { type: 'error', message: 'Commit message cannot be empty', dismissable: true };
336
+ }
337
+ const result = commitChanges(mainRepoPath, manualMessage.trim());
338
+ if (!result.success) {
339
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
340
+ }
341
+ // Retry merge after committing
342
+ return mergePane(pane, context, { mainBranch });
343
+ },
344
+ dismissable: true,
345
+ };
346
+ }
347
+ const result = commitChanges(mainRepoPath, message);
348
+ if (!result.success) {
349
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
350
+ }
351
+ // Retry merge after committing
352
+ return mergePane(pane, context, { mainBranch });
353
+ }
354
+ if (optionId === 'commit_manual') {
355
+ const { stageAllChanges } = await import('../utils/mergeValidation.js');
356
+ // Stage all changes first
357
+ const stageResult = stageAllChanges(mainRepoPath);
358
+ if (!stageResult.success) {
359
+ return { type: 'error', message: `Failed to stage changes: ${stageResult.error}`, dismissable: true };
360
+ }
361
+ return {
362
+ type: 'input',
363
+ title: 'Enter Commit Message',
364
+ message: 'Write a commit message for the changes in main:',
365
+ placeholder: 'feat: add new feature',
366
+ onSubmit: async (message) => {
367
+ if (!message || !message.trim()) {
368
+ return { type: 'error', message: 'Commit message cannot be empty', dismissable: true };
369
+ }
370
+ const result = commitChanges(mainRepoPath, message.trim());
371
+ if (!result.success) {
372
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
373
+ }
374
+ // Retry merge after committing
375
+ return mergePane(pane, context, { mainBranch });
376
+ },
377
+ dismissable: true,
378
+ };
379
+ }
380
+ return { type: 'info', message: 'Unknown option', dismissable: true };
381
+ },
382
+ dismissable: true,
383
+ };
384
+ }
385
+ // Handle worktree uncommitted changes
386
+ if (worktreeUncommitted) {
387
+ return {
388
+ type: 'choice',
389
+ title: 'Worktree Has Uncommitted Changes',
390
+ message: `Changes in:\n${worktreeUncommitted.files.slice(0, 5).join('\n')}${worktreeUncommitted.files.length > 5 ? '\n...' : ''}`,
391
+ options: [
392
+ {
393
+ id: 'commit_automatic',
394
+ label: 'AI commit (automatic)',
395
+ description: 'Auto-generate and commit immediately',
396
+ default: true,
397
+ },
398
+ {
399
+ id: 'commit_ai_editable',
400
+ label: 'AI commit (editable)',
401
+ description: 'Generate message from diff, edit before commit',
402
+ },
403
+ {
404
+ id: 'commit_manual',
405
+ label: 'Manual commit message',
406
+ description: 'Write your own commit message',
407
+ },
408
+ {
409
+ id: 'cancel',
410
+ label: 'Cancel merge',
411
+ description: 'Resolve manually later',
412
+ },
413
+ ],
414
+ onSelect: async (optionId) => {
415
+ if (optionId === 'cancel') {
416
+ return { type: 'info', message: 'Merge cancelled', dismissable: true };
417
+ }
418
+ if (optionId === 'commit_ai_editable') {
419
+ try {
420
+ const { stageAllChanges } = await import('../utils/mergeValidation.js');
421
+ const { getComprehensiveDiff } = await import('../utils/aiMerge.js');
422
+ // Stage all changes first
423
+ const stageResult = stageAllChanges(pane.worktreePath);
424
+ if (!stageResult.success) {
425
+ return { type: 'error', message: `Failed to stage changes: ${stageResult.error}`, dismissable: true };
426
+ }
427
+ // Get diff and generate message
428
+ const { diff, summary } = getComprehensiveDiff(pane.worktreePath);
429
+ console.error('[worktree commit_ai_editable] Calling generateCommitMessageSafe');
430
+ const generatedMessage = await generateCommitMessageSafe(pane.worktreePath);
431
+ console.error('[worktree commit_ai_editable] Generated message:', generatedMessage);
432
+ // If AI generation failed, fall back to manual with explanation
433
+ if (!generatedMessage) {
434
+ console.error('[worktree commit_ai_editable] Falling back to manual input');
435
+ return {
436
+ type: 'input',
437
+ title: 'Enter Commit Message',
438
+ message: `⚠️ Auto-generation failed or timed out. Please write a commit message manually.\n\nFiles changed:\n${summary}`,
439
+ placeholder: 'feat: add new feature',
440
+ onSubmit: async (message) => {
441
+ if (!message || !message.trim()) {
442
+ return { type: 'error', message: 'Commit message cannot be empty', dismissable: true };
443
+ }
444
+ const result = commitChanges(pane.worktreePath, message.trim());
445
+ if (!result.success) {
446
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
447
+ }
448
+ // Retry merge after committing
449
+ return mergePane(pane, context, { mainBranch });
450
+ },
451
+ dismissable: true,
452
+ };
453
+ }
454
+ console.error('[worktree commit_ai_editable] Returning editable input with generated message');
455
+ return {
456
+ type: 'input',
457
+ title: 'Review & Edit Commit Message',
458
+ message: `Files changed:\n${summary}\n\nGenerated message (edit as needed):`,
459
+ placeholder: 'feat: add new feature',
460
+ defaultValue: generatedMessage,
461
+ onSubmit: async (message) => {
462
+ if (!message || !message.trim()) {
463
+ return { type: 'error', message: 'Commit message cannot be empty', dismissable: true };
464
+ }
465
+ const result = commitChanges(pane.worktreePath, message.trim());
466
+ if (!result.success) {
467
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
468
+ }
469
+ // Retry merge after committing
470
+ return mergePane(pane, context, { mainBranch });
471
+ },
472
+ dismissable: true,
473
+ };
474
+ }
475
+ catch (error) {
476
+ console.error('[worktree commit_ai_editable] Unexpected error:', error);
477
+ return {
478
+ type: 'error',
479
+ message: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
480
+ dismissable: true,
481
+ };
482
+ }
483
+ }
484
+ if (optionId === 'commit_automatic') {
485
+ const { stageAllChanges } = await import('../utils/mergeValidation.js');
486
+ const { getComprehensiveDiff } = await import('../utils/aiMerge.js');
487
+ // Stage all changes first
488
+ const stageResult = stageAllChanges(pane.worktreePath);
489
+ if (!stageResult.success) {
490
+ return { type: 'error', message: `Failed to stage changes: ${stageResult.error}`, dismissable: true };
491
+ }
492
+ const message = await generateCommitMessageSafe(pane.worktreePath);
493
+ // If AI generation failed, fall back to manual input
494
+ if (!message) {
495
+ const { summary } = getComprehensiveDiff(pane.worktreePath);
496
+ return {
497
+ type: 'input',
498
+ title: 'Enter Commit Message',
499
+ message: `⚠️ Auto-generation failed or timed out. Please write a commit message manually.\n\nFiles changed:\n${summary}`,
500
+ placeholder: 'feat: add new feature',
501
+ onSubmit: async (manualMessage) => {
502
+ if (!manualMessage || !manualMessage.trim()) {
503
+ return { type: 'error', message: 'Commit message cannot be empty', dismissable: true };
504
+ }
505
+ const result = commitChanges(pane.worktreePath, manualMessage.trim());
506
+ if (!result.success) {
507
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
508
+ }
509
+ // Retry merge after committing
510
+ return mergePane(pane, context, { mainBranch });
511
+ },
512
+ dismissable: true,
513
+ };
514
+ }
515
+ const result = commitChanges(pane.worktreePath, message);
516
+ if (!result.success) {
517
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
518
+ }
519
+ // Retry merge after committing
520
+ return mergePane(pane, context, { mainBranch });
521
+ }
522
+ if (optionId === 'commit_manual') {
523
+ const { stageAllChanges } = await import('../utils/mergeValidation.js');
524
+ // Stage all changes first
525
+ const stageResult = stageAllChanges(pane.worktreePath);
526
+ if (!stageResult.success) {
527
+ return { type: 'error', message: `Failed to stage changes: ${stageResult.error}`, dismissable: true };
528
+ }
529
+ return {
530
+ type: 'input',
531
+ title: 'Enter Commit Message',
532
+ message: 'Write a commit message for the changes:',
533
+ placeholder: 'feat: add new feature',
534
+ onSubmit: async (message) => {
535
+ if (!message || !message.trim()) {
536
+ return { type: 'error', message: 'Commit message cannot be empty', dismissable: true };
537
+ }
538
+ const result = commitChanges(pane.worktreePath, message.trim());
539
+ if (!result.success) {
540
+ return { type: 'error', message: `Commit failed: ${result.error}`, dismissable: true };
541
+ }
542
+ // Retry merge after committing
543
+ return mergePane(pane, context, { mainBranch });
544
+ },
545
+ dismissable: true,
546
+ };
547
+ }
548
+ return { type: 'info', message: 'Unknown option', dismissable: true };
549
+ },
550
+ dismissable: true,
551
+ };
552
+ }
553
+ // Handle merge conflicts
554
+ if (mergeConflict) {
555
+ return {
556
+ type: 'choice',
557
+ title: 'Merge Conflicts Detected',
558
+ message: `Conflicts will occur in:\n${mergeConflict.files.slice(0, 5).join('\n')}${mergeConflict.files.length > 5 ? '\n...' : ''}`,
559
+ options: [
560
+ {
561
+ id: 'ai_merge',
562
+ label: 'Try AI-assisted merge',
563
+ description: 'Let AI intelligently combine both versions',
564
+ default: true,
565
+ },
566
+ {
567
+ id: 'manual_merge',
568
+ label: 'Manual resolution',
569
+ description: 'Jump to pane to resolve conflicts',
570
+ },
571
+ {
572
+ id: 'cancel',
573
+ label: 'Cancel merge',
574
+ description: 'Do nothing',
575
+ },
576
+ ],
577
+ onSelect: async (optionId) => {
578
+ if (optionId === 'cancel') {
579
+ return { type: 'info', message: 'Merge cancelled', dismissable: true };
580
+ }
581
+ if (optionId === 'manual_merge') {
582
+ // Start the merge process and let user resolve manually
583
+ return executeMergeWithConflictHandling(pane, context, mainBranch, mainRepoPath, 'manual');
584
+ }
585
+ if (optionId === 'ai_merge') {
586
+ // Attempt AI-assisted merge via new pane
587
+ return createConflictResolutionPaneForMerge(pane, context, mainBranch, mainRepoPath);
588
+ }
589
+ return { type: 'info', message: 'Unknown option', dismissable: true };
590
+ },
591
+ dismissable: true,
592
+ };
593
+ }
594
+ // Generic issue display
595
+ return {
596
+ type: 'error',
597
+ title: 'Merge Issues Detected',
598
+ message: issues.map((i) => i.message).join('\n'),
599
+ dismissable: true,
600
+ };
601
+ }
602
+ /**
603
+ * Create a new pane for AI-assisted conflict resolution
604
+ */
605
+ async function createConflictResolutionPaneForMerge(pane, context, targetBranch, targetRepoPath) {
606
+ // First, check which agents are available
607
+ const { findClaudeCommand, findOpencodeCommand } = await import('../utils/agentDetection.js');
608
+ const availableAgents = [];
609
+ if (await findClaudeCommand())
610
+ availableAgents.push('claude');
611
+ if (await findOpencodeCommand())
612
+ availableAgents.push('opencode');
613
+ if (availableAgents.length === 0) {
614
+ return {
615
+ type: 'error',
616
+ message: 'No AI agents available. Please install claude or opencode.',
617
+ dismissable: true,
618
+ };
619
+ }
620
+ // If multiple agents available, ask user to choose
621
+ if (availableAgents.length > 1) {
622
+ return {
623
+ type: 'choice',
624
+ title: 'Choose AI Agent for Conflict Resolution',
625
+ message: 'Which agent would you like to use to resolve merge conflicts?',
626
+ options: availableAgents.map(agent => ({
627
+ id: agent,
628
+ label: agent === 'claude' ? 'Claude Code' : 'OpenCode',
629
+ description: agent === 'claude' ? 'Anthropic Claude' : 'Open-source alternative',
630
+ default: agent === 'claude',
631
+ })),
632
+ onSelect: async (agentId) => {
633
+ return createAndLaunchConflictPane(pane, context, targetBranch, targetRepoPath, agentId);
634
+ },
635
+ dismissable: true,
636
+ };
637
+ }
638
+ // Only one agent available, use it directly
639
+ return createAndLaunchConflictPane(pane, context, targetBranch, targetRepoPath, availableAgents[0]);
640
+ }
641
+ /**
642
+ * Actually create and launch the conflict resolution pane
643
+ */
644
+ async function createAndLaunchConflictPane(pane, context, targetBranch, targetRepoPath, agent) {
645
+ try {
646
+ const { createConflictResolutionPane } = await import('../utils/conflictResolutionPane.js');
647
+ // Create the new pane
648
+ const conflictPane = await createConflictResolutionPane({
649
+ sourceBranch: pane.slug,
650
+ targetBranch,
651
+ targetRepoPath,
652
+ agent,
653
+ projectName: context.projectName,
654
+ existingPanes: context.panes,
655
+ });
656
+ // Add the new pane to the panes list
657
+ const updatedPanes = [...context.panes, conflictPane];
658
+ await context.savePanes(updatedPanes);
659
+ // Notify about the new pane
660
+ if (context.onPaneUpdate) {
661
+ context.onPaneUpdate(conflictPane);
662
+ }
663
+ return {
664
+ type: 'navigation',
665
+ title: 'Conflict Resolution Pane Created',
666
+ message: `Created pane "${conflictPane.slug}" with ${agent} to help resolve conflicts. Switch to it to see the AI working.`,
667
+ targetPaneId: conflictPane.id,
668
+ dismissable: true,
669
+ };
670
+ }
671
+ catch (error) {
672
+ return {
673
+ type: 'error',
674
+ message: `Failed to create conflict resolution pane: ${error instanceof Error ? error.message : String(error)}`,
675
+ dismissable: true,
676
+ };
677
+ }
678
+ }
679
+ /**
680
+ * Execute merge with conflict handling
681
+ */
682
+ async function executeMergeWithConflictHandling(pane, context, mainBranch, mainRepoPath, strategy) {
683
+ const { mergeMainIntoWorktree, getConflictingFiles, completeMerge } = await import('../utils/mergeExecution.js');
684
+ // Step 1: Merge main into worktree
685
+ const result = mergeMainIntoWorktree(pane.worktreePath, mainBranch);
686
+ if (!result.success && result.needsManualResolution) {
687
+ if (strategy === 'ai') {
688
+ // Try AI resolution
689
+ const { aiResolveAllConflicts } = await import('../utils/aiMerge.js');
690
+ const aiResult = await aiResolveAllConflicts(pane.worktreePath, result.conflictFiles || []);
691
+ if (aiResult.success) {
692
+ // AI resolved all conflicts, complete the merge
693
+ const completeResult = completeMerge(pane.worktreePath, 'Merge with AI-resolved conflicts');
694
+ if (completeResult.success) {
695
+ // Continue with the second phase of merge
696
+ return executeMerge(pane, context, mainBranch, mainRepoPath);
697
+ }
698
+ else {
699
+ return {
700
+ type: 'error',
701
+ message: `Failed to complete merge: ${completeResult.error}`,
702
+ dismissable: true,
703
+ };
704
+ }
705
+ }
706
+ else {
707
+ // AI couldn't resolve, fall back to manual
708
+ return {
709
+ type: 'error',
710
+ title: 'AI Merge Failed',
711
+ message: `AI couldn't resolve conflicts in: ${aiResult.failedFiles.join(', ')}.\nPlease resolve manually.`,
712
+ dismissable: true,
713
+ };
714
+ }
715
+ }
716
+ else {
717
+ // Manual resolution - jump to pane
718
+ return {
719
+ type: 'navigation',
720
+ title: 'Manual Conflict Resolution',
721
+ message: `Conflicts in: ${result.conflictFiles?.join(', ')}.\nResolve in the pane, then try merge again.`,
722
+ targetPaneId: pane.id,
723
+ dismissable: true,
724
+ };
725
+ }
726
+ }
727
+ if (!result.success) {
728
+ return {
729
+ type: 'error',
730
+ message: `Merge failed: ${result.error}`,
731
+ dismissable: true,
732
+ };
733
+ }
734
+ // No conflicts, proceed with the main merge
735
+ return executeMerge(pane, context, mainBranch, mainRepoPath);
736
+ }
737
+ /**
738
+ * Execute the actual merge operation (called after all pre-checks pass)
739
+ */
740
+ async function executeMerge(pane, context, mainBranch, mainRepoPath) {
741
+ const { mergeWorktreeIntoMain, cleanupAfterMerge } = await import('../utils/mergeExecution.js');
742
+ // Step 2: Merge worktree into main
743
+ const result = mergeWorktreeIntoMain(mainRepoPath, pane.slug);
744
+ if (!result.success) {
745
+ return {
746
+ type: 'error',
747
+ title: 'Merge Failed',
748
+ message: `Failed to merge into ${mainBranch}: ${result.error}`,
749
+ dismissable: true,
750
+ };
751
+ }
752
+ // Merge successful! Ask about cleanup
753
+ return {
754
+ type: 'confirm',
755
+ title: 'Merge Complete',
756
+ message: `Successfully merged "${pane.slug}" into ${mainBranch}. Close the pane and cleanup worktree?`,
757
+ confirmLabel: 'Yes, close it',
758
+ cancelLabel: 'No, keep it',
759
+ onConfirm: async () => {
760
+ // Cleanup worktree and branch
761
+ const cleanup = cleanupAfterMerge(mainRepoPath, pane.worktreePath, pane.slug);
762
+ if (!cleanup.success) {
763
+ return {
764
+ type: 'error',
765
+ message: `Merge succeeded but cleanup failed: ${cleanup.error}`,
766
+ dismissable: true,
767
+ };
768
+ }
769
+ // Close the pane
770
+ return executeCloseOption(pane, context, 'kill_only');
771
+ },
772
+ onCancel: async () => {
773
+ return {
774
+ type: 'success',
775
+ message: 'Merge complete. Pane kept open.',
776
+ dismissable: true,
777
+ };
778
+ },
779
+ };
780
+ }
781
+ /**
782
+ * Rename a pane
783
+ */
784
+ export async function renamePane(pane, context) {
785
+ return {
786
+ type: 'input',
787
+ title: 'Rename Pane',
788
+ message: 'Enter new name for pane:',
789
+ placeholder: pane.slug,
790
+ defaultValue: pane.slug,
791
+ onSubmit: async (value) => {
792
+ if (!value || value === pane.slug) {
793
+ return {
794
+ type: 'info',
795
+ message: 'Rename cancelled',
796
+ dismissable: true,
797
+ };
798
+ }
799
+ // Update pane name
800
+ const updatedPane = {
801
+ ...pane,
802
+ slug: value,
803
+ };
804
+ // Update tmux pane title
805
+ try {
806
+ execSync(`tmux select-pane -t '${pane.paneId}' -T "${value}"`, { stdio: 'pipe' });
807
+ }
808
+ catch {
809
+ // Ignore if title update fails
810
+ }
811
+ // Update in panes list
812
+ const updatedPanes = context.panes.map(p => p.id === pane.id ? updatedPane : p);
813
+ await context.savePanes(updatedPanes);
814
+ if (context.onPaneUpdate) {
815
+ context.onPaneUpdate(updatedPane);
816
+ }
817
+ return {
818
+ type: 'success',
819
+ message: `Renamed to "${value}"`,
820
+ dismissable: true,
821
+ };
822
+ },
823
+ };
824
+ }
825
+ /**
826
+ * Copy worktree path to clipboard
827
+ */
828
+ export async function copyPath(pane, context) {
829
+ if (!pane.worktreePath) {
830
+ return {
831
+ type: 'error',
832
+ message: 'This pane has no worktree path',
833
+ dismissable: true,
834
+ };
835
+ }
836
+ try {
837
+ // Try to copy to clipboard (works on macOS)
838
+ execSync(`echo "${pane.worktreePath}" | pbcopy`, { stdio: 'pipe' });
839
+ return {
840
+ type: 'success',
841
+ message: `Path copied: ${pane.worktreePath}`,
842
+ dismissable: true,
843
+ };
844
+ }
845
+ catch {
846
+ // If clipboard copy fails, just show the path
847
+ return {
848
+ type: 'info',
849
+ message: `Path: ${pane.worktreePath}`,
850
+ dismissable: true,
851
+ };
852
+ }
853
+ }
854
+ /**
855
+ * Open worktree in external editor
856
+ */
857
+ export async function openInEditor(pane, context, params) {
858
+ if (!pane.worktreePath) {
859
+ return {
860
+ type: 'error',
861
+ message: 'This pane has no worktree to open',
862
+ dismissable: true,
863
+ };
864
+ }
865
+ const editor = params?.editor || process.env.EDITOR || 'code';
866
+ try {
867
+ execSync(`${editor} "${pane.worktreePath}"`, { stdio: 'pipe' });
868
+ return {
869
+ type: 'success',
870
+ message: `Opened in ${editor}`,
871
+ dismissable: true,
872
+ };
873
+ }
874
+ catch (error) {
875
+ return {
876
+ type: 'error',
877
+ message: `Failed to open in editor: ${error}`,
878
+ dismissable: true,
879
+ };
880
+ }
881
+ }
882
+ /**
883
+ * Duplicate a pane (create a new pane with the same prompt)
884
+ */
885
+ export async function duplicatePane(pane, context) {
886
+ return {
887
+ type: 'confirm',
888
+ title: 'Duplicate Pane',
889
+ message: `Create a new pane with the same prompt as "${pane.slug}"?`,
890
+ confirmLabel: 'Duplicate',
891
+ cancelLabel: 'Cancel',
892
+ onConfirm: async () => {
893
+ // This would trigger the new pane creation flow with the same prompt
894
+ return {
895
+ type: 'info',
896
+ message: 'Duplication not yet implemented',
897
+ data: { action: 'create_pane', prompt: pane.prompt, agent: pane.agent },
898
+ dismissable: true,
899
+ };
900
+ },
901
+ };
902
+ }
903
+ /**
904
+ * Toggle autopilot mode for a pane
905
+ */
906
+ export async function toggleAutopilot(pane, context) {
907
+ try {
908
+ // Toggle the autopilot setting
909
+ const newAutopilotState = !pane.autopilot;
910
+ // Update the pane
911
+ const updatedPanes = context.panes.map(p => p.id === pane.id ? { ...p, autopilot: newAutopilotState } : p);
912
+ // Save the updated panes
913
+ await context.savePanes(updatedPanes);
914
+ // Notify about the update
915
+ if (context.onPaneUpdate) {
916
+ context.onPaneUpdate({ ...pane, autopilot: newAutopilotState });
917
+ }
918
+ return {
919
+ type: 'success',
920
+ message: `Autopilot ${newAutopilotState ? 'enabled' : 'disabled'} for "${pane.slug}"`,
921
+ dismissable: true,
922
+ };
923
+ }
924
+ catch (error) {
925
+ return {
926
+ type: 'error',
927
+ message: `Failed to toggle autopilot: ${error instanceof Error ? error.message : 'Unknown error'}`,
928
+ dismissable: true,
929
+ };
930
+ }
931
+ }
932
+ //# sourceMappingURL=paneActions.js.map