dmux 2.2.1 → 3.0.1
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.
- package/dist/DmuxApp.d.ts.map +1 -1
- package/dist/DmuxApp.js +433 -179
- package/dist/DmuxApp.js.map +1 -1
- package/dist/MergePane.d.ts.map +1 -1
- package/dist/MergePane.js +4 -6
- package/dist/MergePane.js.map +1 -1
- package/dist/PaneAnalyzer.d.ts +45 -0
- package/dist/PaneAnalyzer.d.ts.map +1 -0
- package/dist/PaneAnalyzer.js +278 -0
- package/dist/PaneAnalyzer.js.map +1 -0
- package/dist/_plugin-vue_export-helper.css +1 -0
- package/dist/actions/index.d.ts +19 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +54 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/paneActions.d.ts +45 -0
- package/dist/actions/paneActions.d.ts.map +1 -0
- package/dist/actions/paneActions.js +932 -0
- package/dist/actions/paneActions.js.map +1 -0
- package/dist/actions/types.d.ts +101 -0
- package/dist/actions/types.d.ts.map +1 -0
- package/dist/actions/types.js +129 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/adapters/apiActionHandler.d.ts +64 -0
- package/dist/adapters/apiActionHandler.d.ts.map +1 -0
- package/dist/adapters/apiActionHandler.js +170 -0
- package/dist/adapters/apiActionHandler.js.map +1 -0
- package/dist/adapters/tuiActionHandler.d.ts +57 -0
- package/dist/adapters/tuiActionHandler.d.ts.map +1 -0
- package/dist/adapters/tuiActionHandler.js +152 -0
- package/dist/adapters/tuiActionHandler.js.map +1 -0
- package/dist/chunks/_plugin-vue_export-helper-Cvoq67hi.js +28 -0
- package/dist/components/ActionChoiceDialog.d.ts +16 -0
- package/dist/components/ActionChoiceDialog.d.ts.map +1 -0
- package/dist/components/ActionChoiceDialog.js +29 -0
- package/dist/components/ActionChoiceDialog.js.map +1 -0
- package/dist/components/ActionConfirmDialog.d.ts +16 -0
- package/dist/components/ActionConfirmDialog.d.ts.map +1 -0
- package/dist/components/ActionConfirmDialog.js +31 -0
- package/dist/components/ActionConfirmDialog.js.map +1 -0
- package/dist/components/ActionInputDialog.d.ts +16 -0
- package/dist/components/ActionInputDialog.d.ts.map +1 -0
- package/dist/components/ActionInputDialog.js +49 -0
- package/dist/components/ActionInputDialog.js.map +1 -0
- package/dist/components/ActionProgressDialog.d.ts +13 -0
- package/dist/components/ActionProgressDialog.d.ts.map +1 -0
- package/dist/components/ActionProgressDialog.js +20 -0
- package/dist/components/ActionProgressDialog.js.map +1 -0
- package/dist/components/FooterHelp.d.ts +2 -0
- package/dist/components/FooterHelp.d.ts.map +1 -1
- package/dist/components/FooterHelp.js +9 -2
- package/dist/components/FooterHelp.js.map +1 -1
- package/dist/components/KebabMenu.d.ts +10 -0
- package/dist/components/KebabMenu.d.ts.map +1 -0
- package/dist/components/KebabMenu.js +18 -0
- package/dist/components/KebabMenu.js.map +1 -0
- package/dist/components/LoadingIndicator.d.ts.map +1 -1
- package/dist/components/LoadingIndicator.js +5 -5
- package/dist/components/LoadingIndicator.js.map +1 -1
- package/dist/components/PaneCard.d.ts +1 -0
- package/dist/components/PaneCard.d.ts.map +1 -1
- package/dist/components/PaneCard.js +21 -20
- package/dist/components/PaneCard.js.map +1 -1
- package/dist/components/PanesGrid.d.ts +1 -0
- package/dist/components/PanesGrid.d.ts.map +1 -1
- package/dist/components/PanesGrid.js +5 -4
- package/dist/components/PanesGrid.js.map +1 -1
- package/dist/components/QRCode.d.ts +7 -0
- package/dist/components/QRCode.d.ts.map +1 -0
- package/dist/components/QRCode.js +19 -0
- package/dist/components/QRCode.js.map +1 -0
- package/dist/components/Spinner.d.ts +10 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +15 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/dashboard.html +14 -0
- package/dist/dashboard.js +2 -0
- package/dist/hooks/useActionSystem.d.ts +31 -0
- package/dist/hooks/useActionSystem.d.ts.map +1 -0
- package/dist/hooks/useActionSystem.js +95 -0
- package/dist/hooks/useActionSystem.js.map +1 -0
- package/dist/hooks/useAgentStatus.d.ts +4 -3
- package/dist/hooks/useAgentStatus.d.ts.map +1 -1
- package/dist/hooks/useAgentStatus.js +45 -194
- package/dist/hooks/useAgentStatus.js.map +1 -1
- package/dist/hooks/usePaneCreation.d.ts +3 -1
- package/dist/hooks/usePaneCreation.d.ts.map +1 -1
- package/dist/hooks/usePaneCreation.js +49 -99
- package/dist/hooks/usePaneCreation.js.map +1 -1
- package/dist/hooks/usePanes.d.ts.map +1 -1
- package/dist/hooks/usePanes.js +6 -3
- package/dist/hooks/usePanes.js.map +1 -1
- package/dist/hooks/useTerminalWidth.d.ts.map +1 -1
- package/dist/hooks/useTerminalWidth.js +18 -1
- package/dist/hooks/useTerminalWidth.js.map +1 -1
- package/dist/hooks/useWorktreeActions.d.ts +2 -1
- package/dist/hooks/useWorktreeActions.d.ts.map +1 -1
- package/dist/hooks/useWorktreeActions.js +9 -1
- package/dist/hooks/useWorktreeActions.js.map +1 -1
- package/dist/index.js +48 -3
- package/dist/index.js.map +1 -1
- package/dist/server/actionsApi.d.ts +37 -0
- package/dist/server/actionsApi.d.ts.map +1 -0
- package/dist/server/actionsApi.js +256 -0
- package/dist/server/actionsApi.js.map +1 -0
- package/dist/server/embedded-assets.d.ts +13 -0
- package/dist/server/embedded-assets.d.ts.map +1 -0
- package/dist/server/embedded-assets.js +5038 -0
- package/dist/server/embedded-assets.js.map +1 -0
- package/dist/server/index.d.ts +21 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +99 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/routes.js +672 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/static.d.ts +6 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +3040 -0
- package/dist/server/static.js.map +1 -0
- package/dist/services/ConfigWatcher.d.ts +20 -0
- package/dist/services/ConfigWatcher.d.ts.map +1 -0
- package/dist/services/ConfigWatcher.js +75 -0
- package/dist/services/ConfigWatcher.js.map +1 -0
- package/dist/services/PaneWorkerManager.d.ts +69 -0
- package/dist/services/PaneWorkerManager.d.ts.map +1 -0
- package/dist/services/PaneWorkerManager.js +272 -0
- package/dist/services/PaneWorkerManager.js.map +1 -0
- package/dist/services/StatusDetector.d.ts +87 -0
- package/dist/services/StatusDetector.d.ts.map +1 -0
- package/dist/services/StatusDetector.js +387 -0
- package/dist/services/StatusDetector.js.map +1 -0
- package/dist/services/TerminalDiffer.d.ts +85 -0
- package/dist/services/TerminalDiffer.d.ts.map +1 -0
- package/dist/services/TerminalDiffer.js +499 -0
- package/dist/services/TerminalDiffer.js.map +1 -0
- package/dist/services/TerminalStreamer.d.ts +80 -0
- package/dist/services/TerminalStreamer.d.ts.map +1 -0
- package/dist/services/TerminalStreamer.js +490 -0
- package/dist/services/TerminalStreamer.js.map +1 -0
- package/dist/services/TunnelService.d.ts +9 -0
- package/dist/services/TunnelService.d.ts.map +1 -0
- package/dist/services/TunnelService.js +34 -0
- package/dist/services/TunnelService.js.map +1 -0
- package/dist/services/WorkerMessageBus.d.ts +48 -0
- package/dist/services/WorkerMessageBus.d.ts.map +1 -0
- package/dist/services/WorkerMessageBus.js +120 -0
- package/dist/services/WorkerMessageBus.js.map +1 -0
- package/dist/shared/StateManager.d.ts +34 -0
- package/dist/shared/StateManager.d.ts.map +1 -0
- package/dist/shared/StateManager.js +108 -0
- package/dist/shared/StateManager.js.map +1 -0
- package/dist/shared/StreamProtocol.d.ts +75 -0
- package/dist/shared/StreamProtocol.d.ts.map +1 -0
- package/dist/shared/StreamProtocol.js +37 -0
- package/dist/shared/StreamProtocol.js.map +1 -0
- package/dist/terminal.html +17 -0
- package/dist/terminal.js +3 -0
- package/dist/types.d.ts +21 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agentDetection.d.ts +18 -0
- package/dist/utils/agentDetection.d.ts.map +1 -0
- package/dist/utils/agentDetection.js +73 -0
- package/dist/utils/agentDetection.js.map +1 -0
- package/dist/utils/aiMerge.d.ts +35 -0
- package/dist/utils/aiMerge.d.ts.map +1 -0
- package/dist/utils/aiMerge.js +302 -0
- package/dist/utils/aiMerge.js.map +1 -0
- package/dist/utils/conflictResolutionPane.d.ts +19 -0
- package/dist/utils/conflictResolutionPane.d.ts.map +1 -0
- package/dist/utils/conflictResolutionPane.js +214 -0
- package/dist/utils/conflictResolutionPane.js.map +1 -0
- package/dist/utils/mergeExecution.d.ts +52 -0
- package/dist/utils/mergeExecution.d.ts.map +1 -0
- package/dist/utils/mergeExecution.js +192 -0
- package/dist/utils/mergeExecution.js.map +1 -0
- package/dist/utils/mergeValidation.d.ts +67 -0
- package/dist/utils/mergeValidation.d.ts.map +1 -0
- package/dist/utils/mergeValidation.js +213 -0
- package/dist/utils/mergeValidation.js.map +1 -0
- package/dist/utils/paneCreation.d.ts +17 -0
- package/dist/utils/paneCreation.d.ts.map +1 -0
- package/dist/utils/paneCreation.js +274 -0
- package/dist/utils/paneCreation.js.map +1 -0
- package/dist/utils/port.d.ts +5 -0
- package/dist/utils/port.d.ts.map +1 -0
- package/dist/utils/port.js +54 -0
- package/dist/utils/port.js.map +1 -0
- package/dist/utils/slug.d.ts.map +1 -1
- package/dist/utils/slug.js +32 -25
- package/dist/utils/slug.js.map +1 -1
- package/dist/workers/PaneWorker.d.ts +2 -0
- package/dist/workers/PaneWorker.d.ts.map +1 -0
- package/dist/workers/PaneWorker.js +362 -0
- package/dist/workers/PaneWorker.js.map +1 -0
- package/dist/workers/WorkerMessages.d.ts +36 -0
- package/dist/workers/WorkerMessages.d.ts.map +1 -0
- package/dist/workers/WorkerMessages.js +9 -0
- package/dist/workers/WorkerMessages.js.map +1 -0
- package/package.json +19 -5
package/dist/DmuxApp.js
CHANGED
|
@@ -11,21 +11,22 @@ import useNavigation from './hooks/useNavigation.js';
|
|
|
11
11
|
import useAutoUpdater from './hooks/useAutoUpdater.js';
|
|
12
12
|
import useAgentDetection from './hooks/useAgentDetection.js';
|
|
13
13
|
import useAgentStatus from './hooks/useAgentStatus.js';
|
|
14
|
-
import useWorktreeActions from './hooks/useWorktreeActions.js';
|
|
15
14
|
import usePaneRunner from './hooks/usePaneRunner.js';
|
|
16
15
|
import usePaneCreation from './hooks/usePaneCreation.js';
|
|
16
|
+
import useActionSystem from './hooks/useActionSystem.js';
|
|
17
17
|
// Utils
|
|
18
18
|
import { applySmartLayout } from './utils/tmux.js';
|
|
19
19
|
import { suggestCommand } from './utils/commands.js';
|
|
20
20
|
import { generateSlug } from './utils/slug.js';
|
|
21
21
|
import { getMainBranch } from './utils/git.js';
|
|
22
|
+
import { StateManager } from './shared/StateManager.js';
|
|
23
|
+
import { getStatusDetector } from './services/StatusDetector.js';
|
|
24
|
+
import { PaneAction, getAvailableActions } from './actions/index.js';
|
|
22
25
|
const require = createRequire(import.meta.url);
|
|
23
26
|
const packageJson = require('../package.json');
|
|
24
27
|
import PanesGrid from './components/PanesGrid.js';
|
|
25
28
|
import NewPaneDialog from './components/NewPaneDialog.js';
|
|
26
29
|
import AgentChoiceDialog from './components/AgentChoiceDialog.js';
|
|
27
|
-
import CloseOptionsDialog from './components/CloseOptionsDialog.js';
|
|
28
|
-
import MergeConfirmationDialog from './components/MergeConfirmationDialog.js';
|
|
29
30
|
import CommandPromptDialog from './components/CommandPromptDialog.js';
|
|
30
31
|
import FileCopyPrompt from './components/FileCopyPrompt.js';
|
|
31
32
|
import LoadingIndicator from './components/LoadingIndicator.js';
|
|
@@ -33,27 +34,35 @@ import RunningIndicator from './components/RunningIndicator.js';
|
|
|
33
34
|
import UpdatingIndicator from './components/UpdatingIndicator.js';
|
|
34
35
|
import CreatingIndicator from './components/CreatingIndicator.js';
|
|
35
36
|
import FooterHelp from './components/FooterHelp.js';
|
|
36
|
-
import
|
|
37
|
-
|
|
37
|
+
import QRCode from './components/QRCode.js';
|
|
38
|
+
import KebabMenu from './components/KebabMenu.js';
|
|
39
|
+
import ActionChoiceDialog from './components/ActionChoiceDialog.js';
|
|
40
|
+
import ActionConfirmDialog from './components/ActionConfirmDialog.js';
|
|
41
|
+
import ActionInputDialog from './components/ActionInputDialog.js';
|
|
42
|
+
import ActionProgressDialog from './components/ActionProgressDialog.js';
|
|
43
|
+
const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdater, serverPort, server }) => {
|
|
38
44
|
/* panes state moved to usePanes */
|
|
39
45
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
40
46
|
const [showNewPaneDialog, setShowNewPaneDialog] = useState(false);
|
|
41
47
|
const [newPanePrompt, setNewPanePrompt] = useState('');
|
|
42
48
|
const [statusMessage, setStatusMessage] = useState('');
|
|
43
|
-
const [showMergeConfirmation, setShowMergeConfirmation] = useState(false);
|
|
44
|
-
const [mergedPane, setMergedPane] = useState(null);
|
|
45
|
-
const [showMergePane, setShowMergePane] = useState(false);
|
|
46
|
-
const [mergingPane, setMergingPane] = useState(null);
|
|
47
|
-
const [showCloseOptions, setShowCloseOptions] = useState(false);
|
|
48
|
-
const [selectedCloseOption, setSelectedCloseOption] = useState(0);
|
|
49
|
-
const [closingPane, setClosingPane] = useState(null);
|
|
50
49
|
const [isCreatingPane, setIsCreatingPane] = useState(false);
|
|
50
|
+
const [showQRCode, setShowQRCode] = useState(false);
|
|
51
|
+
const [tunnelUrl, setTunnelUrl] = useState(null);
|
|
52
|
+
const [isCreatingTunnel, setIsCreatingTunnel] = useState(false);
|
|
53
|
+
// Force repaint trigger - incrementing this causes Ink to re-render
|
|
54
|
+
const [forceRepaintTrigger, setForceRepaintTrigger] = useState(0);
|
|
51
55
|
const { projectSettings, saveSettings } = useProjectSettings(settingsFile);
|
|
52
56
|
const [showCommandPrompt, setShowCommandPrompt] = useState(null);
|
|
53
57
|
const [commandInput, setCommandInput] = useState('');
|
|
54
58
|
const [showFileCopyPrompt, setShowFileCopyPrompt] = useState(false);
|
|
55
59
|
const [currentCommandType, setCurrentCommandType] = useState(null);
|
|
56
60
|
const [runningCommand, setRunningCommand] = useState(false);
|
|
61
|
+
const [quitConfirmMode, setQuitConfirmMode] = useState(false);
|
|
62
|
+
const [showKebabMenu, setShowKebabMenu] = useState(false);
|
|
63
|
+
const [kebabMenuPaneIndex, setKebabMenuPaneIndex] = useState(null);
|
|
64
|
+
const [kebabMenuOption, setKebabMenuOption] = useState(0);
|
|
65
|
+
const [kebabMenuActions, setKebabMenuActions] = useState([]);
|
|
57
66
|
// Update state handled by hook
|
|
58
67
|
const { updateInfo, showUpdateDialog, isUpdating, performUpdate, skipUpdate, dismissUpdate, updateAvailable } = useAutoUpdater(autoUpdater, setStatusMessage);
|
|
59
68
|
const { exit } = useApp();
|
|
@@ -64,17 +73,28 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
64
73
|
const [pendingPrompt, setPendingPrompt] = useState('');
|
|
65
74
|
// Track terminal dimensions for responsive layout
|
|
66
75
|
const terminalWidth = useTerminalWidth();
|
|
67
|
-
// Panes state and persistence
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
const
|
|
76
|
+
// Panes state and persistence (skipLoading will be updated after actionSystem is initialized)
|
|
77
|
+
const { panes, setPanes, isLoading, loadPanes, savePanes } = usePanes(panesFile, false);
|
|
78
|
+
// Track intentionally closed panes to prevent race condition
|
|
79
|
+
// When a user closes a pane, we add it to this set. If the worker detects
|
|
80
|
+
// the pane is gone (which it will), we check this set first before re-saving.
|
|
81
|
+
const intentionallyClosedPanes = React.useRef(new Set());
|
|
82
|
+
// Action system
|
|
83
|
+
const actionSystem = useActionSystem({
|
|
73
84
|
panes,
|
|
74
85
|
savePanes,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
sessionName,
|
|
87
|
+
projectName,
|
|
88
|
+
onPaneRemove: (paneId) => {
|
|
89
|
+
// Mark this pane as intentionally closed
|
|
90
|
+
intentionallyClosedPanes.current.add(paneId);
|
|
91
|
+
const updated = panes.filter(p => p.id !== paneId);
|
|
92
|
+
setPanes(updated);
|
|
93
|
+
// Clean up the tracking after a delay (in case of race conditions)
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
intentionallyClosedPanes.current.delete(paneId);
|
|
96
|
+
}, 5000);
|
|
97
|
+
},
|
|
78
98
|
});
|
|
79
99
|
// Pane runner
|
|
80
100
|
const { copyNonGitFiles, runCommandInternal, monitorTestOutput, monitorDevOutput, attachBackgroundWindow } = usePaneRunner({
|
|
@@ -84,6 +104,21 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
84
104
|
setStatusMessage,
|
|
85
105
|
setRunningCommand,
|
|
86
106
|
});
|
|
107
|
+
// Force repaint helper
|
|
108
|
+
const forceRepaint = () => setForceRepaintTrigger(prev => prev + 1);
|
|
109
|
+
// Force repaint effect - ensures Ink re-renders when trigger changes
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (forceRepaintTrigger > 0) {
|
|
112
|
+
// Small delay to ensure terminal is ready
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
try {
|
|
115
|
+
execSync('tmux refresh-client', { stdio: 'pipe' });
|
|
116
|
+
}
|
|
117
|
+
catch { }
|
|
118
|
+
}, 50);
|
|
119
|
+
return () => clearTimeout(timer);
|
|
120
|
+
}
|
|
121
|
+
}, [forceRepaintTrigger]);
|
|
87
122
|
// Pane creation
|
|
88
123
|
const { openInEditor: openEditor2, createNewPane: createNewPaneHook } = usePaneCreation({
|
|
89
124
|
panes,
|
|
@@ -94,32 +129,89 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
94
129
|
setNewPanePrompt,
|
|
95
130
|
loadPanes,
|
|
96
131
|
panesFile,
|
|
132
|
+
availableAgents,
|
|
133
|
+
forceRepaint,
|
|
97
134
|
});
|
|
135
|
+
// Listen for status updates with analysis data and merge into panes
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
const statusDetector = getStatusDetector();
|
|
138
|
+
const handleStatusUpdate = (event) => {
|
|
139
|
+
setPanes(prevPanes => {
|
|
140
|
+
const updatedPanes = prevPanes.map(pane => {
|
|
141
|
+
if (pane.id === event.paneId) {
|
|
142
|
+
const updated = {
|
|
143
|
+
...pane,
|
|
144
|
+
agentStatus: event.status,
|
|
145
|
+
};
|
|
146
|
+
// Only update analysis fields if they're present in the event (not undefined)
|
|
147
|
+
// This prevents simple status changes from overwriting PaneAnalyzer results
|
|
148
|
+
if (event.optionsQuestion !== undefined) {
|
|
149
|
+
updated.optionsQuestion = event.optionsQuestion;
|
|
150
|
+
}
|
|
151
|
+
if (event.options !== undefined) {
|
|
152
|
+
updated.options = event.options;
|
|
153
|
+
}
|
|
154
|
+
if (event.potentialHarm !== undefined) {
|
|
155
|
+
updated.potentialHarm = event.potentialHarm;
|
|
156
|
+
}
|
|
157
|
+
if (event.summary !== undefined) {
|
|
158
|
+
updated.agentSummary = event.summary;
|
|
159
|
+
}
|
|
160
|
+
if (event.analyzerError !== undefined) {
|
|
161
|
+
updated.analyzerError = event.analyzerError;
|
|
162
|
+
}
|
|
163
|
+
// Clear option dialog data when transitioning away from 'waiting' state
|
|
164
|
+
if (event.status !== 'waiting' && pane.agentStatus === 'waiting') {
|
|
165
|
+
updated.optionsQuestion = undefined;
|
|
166
|
+
updated.options = undefined;
|
|
167
|
+
updated.potentialHarm = undefined;
|
|
168
|
+
}
|
|
169
|
+
// Clear summary when transitioning away from 'idle' state
|
|
170
|
+
if (event.status !== 'idle' && pane.agentStatus === 'idle') {
|
|
171
|
+
updated.agentSummary = undefined;
|
|
172
|
+
}
|
|
173
|
+
// Clear analyzer error when successfully getting a new analysis
|
|
174
|
+
// or when transitioning to 'working' status
|
|
175
|
+
if (event.status === 'working') {
|
|
176
|
+
updated.analyzerError = undefined;
|
|
177
|
+
}
|
|
178
|
+
else if (event.status === 'waiting' || event.status === 'idle') {
|
|
179
|
+
if (event.analyzerError === undefined && (event.optionsQuestion || event.summary)) {
|
|
180
|
+
updated.analyzerError = undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return updated;
|
|
184
|
+
}
|
|
185
|
+
return pane;
|
|
186
|
+
});
|
|
187
|
+
// Persist to disk - ConfigWatcher will handle syncing to StateManager
|
|
188
|
+
savePanes(updatedPanes).catch(err => {
|
|
189
|
+
console.error('Failed to save panes after status update:', err);
|
|
190
|
+
});
|
|
191
|
+
return updatedPanes;
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
statusDetector.on('status-updated', handleStatusUpdate);
|
|
195
|
+
return () => {
|
|
196
|
+
statusDetector.off('status-updated', handleStatusUpdate);
|
|
197
|
+
};
|
|
198
|
+
}, [setPanes, savePanes]);
|
|
199
|
+
// Note: No need to sync panes with StateManager here.
|
|
200
|
+
// The ConfigWatcher automatically updates StateManager when the config file changes.
|
|
201
|
+
// This prevents unnecessary SSE broadcasts on every local state update.
|
|
202
|
+
// Sync settings with StateManager
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
const stateManager = StateManager.getInstance();
|
|
205
|
+
stateManager.updateSettings(projectSettings);
|
|
206
|
+
}, [projectSettings]);
|
|
98
207
|
// Load panes and settings on mount and refresh periodically
|
|
99
208
|
useEffect(() => {
|
|
100
|
-
//
|
|
209
|
+
// SIGTERM should quit immediately (for process management)
|
|
101
210
|
const handleTermination = () => {
|
|
102
|
-
|
|
103
|
-
process.stdout.write('\x1b[2J\x1b[H'); // Clear screen and move to home
|
|
104
|
-
process.stdout.write('\x1b[3J'); // Clear scrollback buffer
|
|
105
|
-
process.stdout.write('\n'.repeat(100)); // Push any remaining content off screen
|
|
106
|
-
// Clear tmux pane
|
|
107
|
-
try {
|
|
108
|
-
execSync('tmux clear-history', { stdio: 'pipe' });
|
|
109
|
-
execSync('tmux send-keys C-l', { stdio: 'pipe' });
|
|
110
|
-
}
|
|
111
|
-
catch { }
|
|
112
|
-
// Wait a moment for clearing to settle, then show goodbye message
|
|
113
|
-
setTimeout(() => {
|
|
114
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
115
|
-
process.stdout.write('\n\n dmux session ended.\n\n');
|
|
116
|
-
process.exit(0);
|
|
117
|
-
}, 100);
|
|
211
|
+
cleanExit();
|
|
118
212
|
};
|
|
119
|
-
process.on('SIGINT', handleTermination);
|
|
120
213
|
process.on('SIGTERM', handleTermination);
|
|
121
214
|
return () => {
|
|
122
|
-
process.removeListener('SIGINT', handleTermination);
|
|
123
215
|
process.removeListener('SIGTERM', handleTermination);
|
|
124
216
|
};
|
|
125
217
|
}, []);
|
|
@@ -133,8 +225,10 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
133
225
|
if (!isLoading &&
|
|
134
226
|
panes.length === 0 &&
|
|
135
227
|
!showNewPaneDialog &&
|
|
136
|
-
!
|
|
137
|
-
!
|
|
228
|
+
!actionSystem.actionState.showConfirmDialog &&
|
|
229
|
+
!actionSystem.actionState.showChoiceDialog &&
|
|
230
|
+
!actionSystem.actionState.showInputDialog &&
|
|
231
|
+
!actionSystem.actionState.showProgressDialog &&
|
|
138
232
|
!showCommandPrompt &&
|
|
139
233
|
!showFileCopyPrompt &&
|
|
140
234
|
!showAgentChoiceDialog &&
|
|
@@ -143,7 +237,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
143
237
|
!isUpdating) {
|
|
144
238
|
setShowNewPaneDialog(true);
|
|
145
239
|
}
|
|
146
|
-
}, [isLoading, panes.length, showNewPaneDialog,
|
|
240
|
+
}, [isLoading, panes.length, showNewPaneDialog, actionSystem.actionState.showConfirmDialog, actionSystem.actionState.showChoiceDialog, actionSystem.actionState.showInputDialog, actionSystem.actionState.showProgressDialog, showCommandPrompt, showFileCopyPrompt, showAgentChoiceDialog, isCreatingPane, runningCommand, isUpdating]);
|
|
147
241
|
// Update checking moved to useAutoUpdater
|
|
148
242
|
// Set default agent choice when detection completes
|
|
149
243
|
useEffect(() => {
|
|
@@ -154,7 +248,18 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
154
248
|
// Monitor agent status across panes (returns a map of pane ID to status)
|
|
155
249
|
const agentStatuses = useAgentStatus({
|
|
156
250
|
panes,
|
|
157
|
-
suspend: showNewPaneDialog ||
|
|
251
|
+
suspend: showNewPaneDialog || actionSystem.actionState.showConfirmDialog || actionSystem.actionState.showChoiceDialog || actionSystem.actionState.showInputDialog || actionSystem.actionState.showProgressDialog || !!showCommandPrompt || showFileCopyPrompt,
|
|
252
|
+
onPaneRemoved: (paneId) => {
|
|
253
|
+
// Check if this pane was intentionally closed
|
|
254
|
+
// If so, don't re-save - the close action already handled it
|
|
255
|
+
if (intentionallyClosedPanes.current.has(paneId)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Pane was removed unexpectedly (e.g., user killed tmux pane manually)
|
|
259
|
+
// Remove it from our tracking
|
|
260
|
+
const updatedPanes = panes.filter(p => p.id !== paneId);
|
|
261
|
+
savePanes(updatedPanes);
|
|
262
|
+
},
|
|
158
263
|
});
|
|
159
264
|
// loadPanes moved to usePanes
|
|
160
265
|
// getPanePositions moved to utils/tmux
|
|
@@ -288,7 +393,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
288
393
|
if (agent === 'claude') {
|
|
289
394
|
// Monitor for Claude Code trust prompt and auto-respond
|
|
290
395
|
const autoApproveTrust = async () => {
|
|
291
|
-
console.error('[TRUST DEBUG] Starting autoApproveTrust monitoring...');
|
|
292
396
|
// Wait for Claude to start up before checking for prompts
|
|
293
397
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
294
398
|
const maxChecks = 100; // 100 checks * 100ms = 10 seconds total
|
|
@@ -331,7 +435,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
331
435
|
const paneContent = execSync(`tmux capture-pane -t '${paneInfo}' -p -S -30`, // Capture last 30 lines
|
|
332
436
|
{ encoding: 'utf-8', stdio: 'pipe' });
|
|
333
437
|
if (i % 10 === 0) { // Log every 10 checks (every second)
|
|
334
|
-
console.error(`[TRUST DEBUG] Check ${i + 1}/${maxChecks}, content preview: "${paneContent.substring(0, 100).replace(/\n/g, '\\n')}"...`);
|
|
335
438
|
}
|
|
336
439
|
// Check if content has stabilized (same for 3 checks = prompt is waiting)
|
|
337
440
|
if (paneContent === lastContent) {
|
|
@@ -350,29 +453,23 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
350
453
|
paneContent.includes('allow') ||
|
|
351
454
|
(paneContent.includes('folder') && paneContent.includes('?'));
|
|
352
455
|
if ((hasTrustPrompt || hasClaudePermissionPrompt) && !promptHandled) {
|
|
353
|
-
console.error(`[TRUST DEBUG] Trust prompt detected! Pattern match: ${hasTrustPrompt}, Permission text: ${hasClaudePermissionPrompt}, Stable count: ${stableContentCount}`);
|
|
354
|
-
console.error(`[TRUST DEBUG] Content that triggered detection: "${paneContent}"`);
|
|
355
456
|
// Content is stable and we found a prompt
|
|
356
457
|
if (stableContentCount >= 2) {
|
|
357
|
-
console.error('[TRUST DEBUG] Content is stable, attempting to auto-approve trust prompt...');
|
|
358
458
|
// Check if this is the new Claude numbered menu format
|
|
359
459
|
const isNewClaudeFormat = /❯\s*1\.\s*Yes,\s*proceed/i.test(paneContent) ||
|
|
360
460
|
/Enter to confirm.*Esc to exit/i.test(paneContent);
|
|
361
461
|
if (isNewClaudeFormat) {
|
|
362
462
|
// For new Claude format, just press Enter to confirm default "Yes, proceed"
|
|
363
|
-
console.error('[TRUST DEBUG] Detected new Claude format, sending Enter to confirm default option');
|
|
364
463
|
execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
|
|
365
464
|
}
|
|
366
465
|
else {
|
|
367
466
|
// Try multiple response methods for older formats
|
|
368
467
|
// Method 1: Send 'y' followed by Enter (most explicit)
|
|
369
|
-
console.error('[TRUST DEBUG] Sending "y" + Enter for legacy format');
|
|
370
468
|
execSync(`tmux send-keys -t '${paneInfo}' 'y'`, { stdio: 'pipe' });
|
|
371
469
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
372
470
|
execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
|
|
373
471
|
// Method 2: Just Enter (if it's a yes/no with default yes)
|
|
374
472
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
375
|
-
console.error('[TRUST DEBUG] Sending additional Enter');
|
|
376
473
|
execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
|
|
377
474
|
}
|
|
378
475
|
// Mark as handled to avoid duplicate responses
|
|
@@ -384,20 +481,16 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
384
481
|
// If trust prompt is gone, check if we need to resend the Claude command
|
|
385
482
|
const promptGone = !trustPromptPatterns.some(p => p.test(updatedContent));
|
|
386
483
|
if (promptGone) {
|
|
387
|
-
console.error('[TRUST DEBUG] Trust prompt appears to be gone');
|
|
388
484
|
// Check if Claude is running or if we need to restart it
|
|
389
485
|
const claudeRunning = updatedContent.includes('Claude') ||
|
|
390
486
|
updatedContent.includes('claude') ||
|
|
391
487
|
updatedContent.includes('Assistant') ||
|
|
392
488
|
(prompt && updatedContent.includes(prompt.substring(0, Math.min(20, prompt.length))));
|
|
393
|
-
console.error(`[TRUST DEBUG] Claude running check: ${claudeRunning}, has $: ${updatedContent.includes('$')}`);
|
|
394
489
|
if (!claudeRunning && !updatedContent.includes('$')) {
|
|
395
|
-
console.error('[TRUST DEBUG] Claude not running after trust approval, restarting...');
|
|
396
490
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
397
491
|
execSync(`tmux send-keys -t '${paneInfo}' '${escapedCmd}'`, { stdio: 'pipe' });
|
|
398
492
|
execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
|
|
399
493
|
}
|
|
400
|
-
console.error('[TRUST DEBUG] Successfully handled the trust prompt');
|
|
401
494
|
break;
|
|
402
495
|
}
|
|
403
496
|
}
|
|
@@ -410,13 +503,11 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
410
503
|
}
|
|
411
504
|
catch (error) {
|
|
412
505
|
// Continue checking, errors are non-fatal
|
|
413
|
-
console.error(`[TRUST DEBUG] Error checking for trust prompt: ${error instanceof Error ? error.message : error}`);
|
|
414
506
|
}
|
|
415
507
|
}
|
|
416
508
|
};
|
|
417
509
|
// Start monitoring for trust prompt in background
|
|
418
510
|
autoApproveTrust().catch(err => {
|
|
419
|
-
console.error(`[TRUST DEBUG] Error in autoApproveTrust: ${err instanceof Error ? err.message : err}`);
|
|
420
511
|
});
|
|
421
512
|
}
|
|
422
513
|
// Keep focus on the new pane
|
|
@@ -425,7 +516,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
425
516
|
const newPane = {
|
|
426
517
|
id: `dmux-${Date.now()}`,
|
|
427
518
|
slug,
|
|
428
|
-
prompt: prompt
|
|
519
|
+
prompt: prompt || 'No initial prompt',
|
|
429
520
|
paneId: paneInfo,
|
|
430
521
|
worktreePath,
|
|
431
522
|
agent
|
|
@@ -541,32 +632,230 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
541
632
|
}
|
|
542
633
|
};
|
|
543
634
|
// Update handling moved to useAutoUpdater
|
|
544
|
-
//
|
|
545
|
-
const
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
process.stdout.write('\x1b[
|
|
549
|
-
|
|
550
|
-
// Clear tmux pane
|
|
635
|
+
// Helper function to clear screen artifacts
|
|
636
|
+
const clearScreen = () => {
|
|
637
|
+
// Multiple clearing strategies to prevent artifacts
|
|
638
|
+
// 1. Clear screen with ANSI codes
|
|
639
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
640
|
+
// 2. Clear tmux history
|
|
551
641
|
try {
|
|
552
642
|
execSync('tmux clear-history', { stdio: 'pipe' });
|
|
553
|
-
execSync('tmux send-keys C-l', { stdio: 'pipe' });
|
|
554
643
|
}
|
|
555
644
|
catch { }
|
|
556
|
-
//
|
|
645
|
+
// 3. Force tmux to refresh the display
|
|
646
|
+
try {
|
|
647
|
+
execSync('tmux refresh-client', { stdio: 'pipe' });
|
|
648
|
+
}
|
|
649
|
+
catch { }
|
|
650
|
+
// 4. Force Ink to repaint after clearing
|
|
651
|
+
// This prevents the blank screen bug by ensuring Ink re-renders
|
|
652
|
+
setForceRepaintTrigger(prev => prev + 1);
|
|
653
|
+
};
|
|
654
|
+
// Cleanup function for exit
|
|
655
|
+
const cleanExit = () => {
|
|
656
|
+
// Clear screen before exiting Ink
|
|
657
|
+
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
658
|
+
// Exit the Ink app (this cleans up the React tree)
|
|
659
|
+
exit();
|
|
660
|
+
// Give Ink a moment to clean up its rendering, then do final cleanup
|
|
557
661
|
setTimeout(() => {
|
|
558
|
-
//
|
|
662
|
+
// Multiple aggressive clearing strategies
|
|
663
|
+
process.stdout.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home
|
|
664
|
+
process.stdout.write('\x1b[3J'); // Clear scrollback buffer
|
|
665
|
+
process.stdout.write('\x1b[0m'); // Reset all attributes
|
|
666
|
+
// Clear tmux history and pane
|
|
667
|
+
try {
|
|
668
|
+
execSync('tmux clear-history', { stdio: 'pipe' });
|
|
669
|
+
execSync('tmux send-keys C-l', { stdio: 'pipe' });
|
|
670
|
+
}
|
|
671
|
+
catch { }
|
|
672
|
+
// One more final clear
|
|
559
673
|
process.stdout.write('\x1b[2J\x1b[H');
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
674
|
+
// Show clean goodbye message
|
|
675
|
+
process.stdout.write('\n Run dmux again to resume. Goodbye 👋\n\n');
|
|
676
|
+
// Exit process
|
|
677
|
+
process.exit(0);
|
|
563
678
|
}, 100);
|
|
564
679
|
};
|
|
565
680
|
useInput(async (input, key) => {
|
|
566
|
-
|
|
681
|
+
// Handle Ctrl+C for quit confirmation (must be first, before any other checks)
|
|
682
|
+
if (key.ctrl && input === 'c') {
|
|
683
|
+
if (quitConfirmMode) {
|
|
684
|
+
// Second Ctrl+C - actually quit
|
|
685
|
+
cleanExit();
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
// First Ctrl+C - show confirmation
|
|
689
|
+
setQuitConfirmMode(true);
|
|
690
|
+
// Reset after 3 seconds if user doesn't press Ctrl+C again
|
|
691
|
+
setTimeout(() => {
|
|
692
|
+
setQuitConfirmMode(false);
|
|
693
|
+
}, 3000);
|
|
694
|
+
}
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (isCreatingPane || runningCommand || isUpdating || isLoading || isCreatingTunnel) {
|
|
567
698
|
// Disable input while performing operations or loading
|
|
568
699
|
return;
|
|
569
700
|
}
|
|
701
|
+
// Handle kebab menu navigation
|
|
702
|
+
if (showKebabMenu && kebabMenuPaneIndex !== null) {
|
|
703
|
+
const currentPane = panes[kebabMenuPaneIndex];
|
|
704
|
+
const availableActions = kebabMenuActions;
|
|
705
|
+
if (key.escape) {
|
|
706
|
+
setShowKebabMenu(false);
|
|
707
|
+
setKebabMenuPaneIndex(null);
|
|
708
|
+
setKebabMenuOption(0);
|
|
709
|
+
setKebabMenuActions([]);
|
|
710
|
+
clearScreen();
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
else if (key.upArrow) {
|
|
714
|
+
setKebabMenuOption(Math.max(0, kebabMenuOption - 1));
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
else if (key.downArrow) {
|
|
718
|
+
setKebabMenuOption(Math.min(availableActions.length - 1, kebabMenuOption + 1));
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
else if (key.return) {
|
|
722
|
+
// Execute the selected menu action
|
|
723
|
+
setShowKebabMenu(false);
|
|
724
|
+
clearScreen();
|
|
725
|
+
const selectedAction = availableActions[kebabMenuOption];
|
|
726
|
+
if (selectedAction) {
|
|
727
|
+
// Use the action system to execute
|
|
728
|
+
actionSystem.executeAction(selectedAction.id, currentPane, { mainBranch: getMainBranch() });
|
|
729
|
+
}
|
|
730
|
+
setKebabMenuPaneIndex(null);
|
|
731
|
+
setKebabMenuOption(0);
|
|
732
|
+
setKebabMenuActions([]);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
// Don't process other inputs while menu is open
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
// Handle quit confirm mode - ESC cancels it
|
|
739
|
+
if (quitConfirmMode) {
|
|
740
|
+
if (key.escape) {
|
|
741
|
+
setQuitConfirmMode(false);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
// Allow other inputs to continue (don't return early)
|
|
745
|
+
}
|
|
746
|
+
// Handle QR code view
|
|
747
|
+
if (showQRCode) {
|
|
748
|
+
if (key.escape) {
|
|
749
|
+
setShowQRCode(false);
|
|
750
|
+
}
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
// Handle action system confirm dialog
|
|
754
|
+
if (actionSystem.actionState.showConfirmDialog) {
|
|
755
|
+
if (key.escape) {
|
|
756
|
+
if (actionSystem.actionState.onConfirmNo) {
|
|
757
|
+
actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
|
|
761
|
+
}
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
else if (key.upArrow) {
|
|
765
|
+
actionSystem.setActionState(prev => ({
|
|
766
|
+
...prev,
|
|
767
|
+
confirmSelectedIndex: Math.max(0, prev.confirmSelectedIndex - 1)
|
|
768
|
+
}));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
else if (key.downArrow) {
|
|
772
|
+
actionSystem.setActionState(prev => ({
|
|
773
|
+
...prev,
|
|
774
|
+
confirmSelectedIndex: Math.min(1, prev.confirmSelectedIndex + 1)
|
|
775
|
+
}));
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
else if (key.return) {
|
|
779
|
+
// Execute based on selected index
|
|
780
|
+
if (actionSystem.actionState.confirmSelectedIndex === 0) {
|
|
781
|
+
if (actionSystem.actionState.onConfirmYes) {
|
|
782
|
+
actionSystem.executeCallback(actionSystem.actionState.onConfirmYes);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
if (actionSystem.actionState.onConfirmNo) {
|
|
787
|
+
actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
else if (input === 'y' || input === 'Y') {
|
|
796
|
+
// Shortcut: yes
|
|
797
|
+
if (actionSystem.actionState.onConfirmYes) {
|
|
798
|
+
actionSystem.executeCallback(actionSystem.actionState.onConfirmYes);
|
|
799
|
+
}
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
else if (input === 'n' || input === 'N') {
|
|
803
|
+
// Shortcut: no
|
|
804
|
+
if (actionSystem.actionState.onConfirmNo) {
|
|
805
|
+
actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
// Handle action system input dialog
|
|
815
|
+
if (actionSystem.actionState.showInputDialog) {
|
|
816
|
+
if (key.escape) {
|
|
817
|
+
actionSystem.setActionState(prev => ({ ...prev, showInputDialog: false }));
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
else if (key.return) {
|
|
821
|
+
if (actionSystem.actionState.onInputSubmit) {
|
|
822
|
+
actionSystem.executeCallback(async () => actionSystem.actionState.onInputSubmit(actionSystem.actionState.inputValue));
|
|
823
|
+
}
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
// Let CleanTextInput handle all other key events
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
// Handle action system choice dialog
|
|
830
|
+
if (actionSystem.actionState.showChoiceDialog) {
|
|
831
|
+
if (key.escape) {
|
|
832
|
+
actionSystem.setActionState(prev => ({ ...prev, showChoiceDialog: false }));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
else if (key.upArrow) {
|
|
836
|
+
actionSystem.setActionState(prev => ({
|
|
837
|
+
...prev,
|
|
838
|
+
choiceSelectedIndex: Math.max(0, prev.choiceSelectedIndex - 1)
|
|
839
|
+
}));
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
else if (key.downArrow) {
|
|
843
|
+
const maxIndex = actionSystem.actionState.choiceOptions.length - 1;
|
|
844
|
+
actionSystem.setActionState(prev => ({
|
|
845
|
+
...prev,
|
|
846
|
+
choiceSelectedIndex: Math.min(maxIndex, prev.choiceSelectedIndex + 1)
|
|
847
|
+
}));
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
else if (key.return) {
|
|
851
|
+
const selectedOption = actionSystem.actionState.choiceOptions[actionSystem.actionState.choiceSelectedIndex];
|
|
852
|
+
if (selectedOption && actionSystem.actionState.onChoiceSelect) {
|
|
853
|
+
actionSystem.executeCallback(async () => actionSystem.actionState.onChoiceSelect(selectedOption.id));
|
|
854
|
+
}
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
570
859
|
if (showFileCopyPrompt) {
|
|
571
860
|
if (input === 'y' || input === 'Y') {
|
|
572
861
|
setShowFileCopyPrompt(false);
|
|
@@ -667,10 +956,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
667
956
|
}
|
|
668
957
|
return;
|
|
669
958
|
}
|
|
670
|
-
if (showMergePane) {
|
|
671
|
-
// MergePane handles its own input
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
959
|
if (showNewPaneDialog) {
|
|
675
960
|
if (key.escape) {
|
|
676
961
|
setShowNewPaneDialog(false);
|
|
@@ -683,49 +968,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
683
968
|
// TextInput handles other input events
|
|
684
969
|
return;
|
|
685
970
|
}
|
|
686
|
-
if (showMergeConfirmation) {
|
|
687
|
-
if (input === 'y' || input === 'Y') {
|
|
688
|
-
if (mergedPane) {
|
|
689
|
-
closePane(mergedPane);
|
|
690
|
-
}
|
|
691
|
-
setShowMergeConfirmation(false);
|
|
692
|
-
setMergedPane(null);
|
|
693
|
-
}
|
|
694
|
-
else if (input === 'n' || input === 'N' || key.escape) {
|
|
695
|
-
setShowMergeConfirmation(false);
|
|
696
|
-
setMergedPane(null);
|
|
697
|
-
}
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
if (showCloseOptions) {
|
|
701
|
-
if (key.escape) {
|
|
702
|
-
setShowCloseOptions(false);
|
|
703
|
-
setClosingPane(null);
|
|
704
|
-
setSelectedCloseOption(0);
|
|
705
|
-
}
|
|
706
|
-
else if (key.upArrow) {
|
|
707
|
-
setSelectedCloseOption(Math.max(0, selectedCloseOption - 1));
|
|
708
|
-
}
|
|
709
|
-
else if (key.downArrow) {
|
|
710
|
-
setSelectedCloseOption(Math.min(3, selectedCloseOption + 1));
|
|
711
|
-
}
|
|
712
|
-
else if (key.return && closingPane) {
|
|
713
|
-
handleCloseOption(selectedCloseOption, closingPane).then(() => {
|
|
714
|
-
// Close the dialog after the action is performed
|
|
715
|
-
setShowCloseOptions(false);
|
|
716
|
-
setClosingPane(null);
|
|
717
|
-
setSelectedCloseOption(0);
|
|
718
|
-
}).catch(error => {
|
|
719
|
-
setStatusMessage('Failed to close pane');
|
|
720
|
-
setTimeout(() => setStatusMessage(''), 2000);
|
|
721
|
-
// Also close the dialog on error
|
|
722
|
-
setShowCloseOptions(false);
|
|
723
|
-
setClosingPane(null);
|
|
724
|
-
setSelectedCloseOption(0);
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
971
|
// Handle directional navigation with spatial awareness based on card grid layout
|
|
730
972
|
if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
|
|
731
973
|
let targetIndex = null;
|
|
@@ -746,9 +988,42 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
746
988
|
}
|
|
747
989
|
return;
|
|
748
990
|
}
|
|
749
|
-
if (input === '
|
|
991
|
+
if ((input === 'm' || key.return) && selectedIndex < panes.length) {
|
|
992
|
+
// Open kebab menu for selected pane
|
|
993
|
+
const selectedPane = panes[selectedIndex];
|
|
994
|
+
const actions = getAvailableActions(selectedPane, projectSettings);
|
|
995
|
+
setKebabMenuActions(actions);
|
|
996
|
+
setShowKebabMenu(true);
|
|
997
|
+
setKebabMenuPaneIndex(selectedIndex);
|
|
998
|
+
setKebabMenuOption(0);
|
|
999
|
+
}
|
|
1000
|
+
else if (input === 'q') {
|
|
750
1001
|
cleanExit();
|
|
751
1002
|
}
|
|
1003
|
+
else if (input === 'r' && server) {
|
|
1004
|
+
// Create tunnel if not already created, then show QR code
|
|
1005
|
+
if (!tunnelUrl) {
|
|
1006
|
+
setIsCreatingTunnel(true);
|
|
1007
|
+
setStatusMessage('Creating tunnel...');
|
|
1008
|
+
try {
|
|
1009
|
+
const url = await server.startTunnel();
|
|
1010
|
+
setTunnelUrl(url);
|
|
1011
|
+
setStatusMessage('');
|
|
1012
|
+
setShowQRCode(true);
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
setStatusMessage('Failed to create tunnel');
|
|
1016
|
+
setTimeout(() => setStatusMessage(''), 3000);
|
|
1017
|
+
}
|
|
1018
|
+
finally {
|
|
1019
|
+
setIsCreatingTunnel(false);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
// Tunnel already exists, just show the QR code
|
|
1024
|
+
setShowQRCode(true);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
752
1027
|
else if (!isLoading && (input === 'n' || (key.return && selectedIndex === panes.length))) {
|
|
753
1028
|
// Clear the prompt and show dialog in next tick to prevent 'n' bleeding through
|
|
754
1029
|
setNewPanePrompt('');
|
|
@@ -756,61 +1031,29 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
756
1031
|
return; // Consume the 'n' keystroke so it doesn't propagate
|
|
757
1032
|
}
|
|
758
1033
|
else if (input === 'j' && selectedIndex < panes.length) {
|
|
759
|
-
|
|
1034
|
+
// Jump to pane (NEW: using action system)
|
|
1035
|
+
actionSystem.executeAction(PaneAction.VIEW, panes[selectedIndex]);
|
|
760
1036
|
}
|
|
761
1037
|
else if (input === 'x' && selectedIndex < panes.length) {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
setSelectedCloseOption(0);
|
|
765
|
-
}
|
|
766
|
-
else if (input === 'm' && selectedIndex < panes.length) {
|
|
767
|
-
setMergingPane(panes[selectedIndex]);
|
|
768
|
-
setShowMergePane(true);
|
|
769
|
-
}
|
|
770
|
-
else if (input === 't' && selectedIndex < panes.length) {
|
|
771
|
-
await runCommand('test', panes[selectedIndex]);
|
|
772
|
-
}
|
|
773
|
-
else if (input === 'd' && selectedIndex < panes.length) {
|
|
774
|
-
await runCommand('dev', panes[selectedIndex]);
|
|
775
|
-
}
|
|
776
|
-
else if (input === 'o' && selectedIndex < panes.length) {
|
|
777
|
-
const pane = panes[selectedIndex];
|
|
778
|
-
if (pane.testWindowId || pane.devWindowId) {
|
|
779
|
-
// If both exist, prefer dev (since it's usually more interactive)
|
|
780
|
-
if (pane.devWindowId && pane.devStatus === 'running') {
|
|
781
|
-
await attachBackgroundWindow(pane, 'dev');
|
|
782
|
-
}
|
|
783
|
-
else if (pane.testWindowId) {
|
|
784
|
-
await attachBackgroundWindow(pane, 'test');
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
else {
|
|
788
|
-
setStatusMessage('No test or dev window to open');
|
|
789
|
-
setTimeout(() => setStatusMessage(''), 2000);
|
|
790
|
-
}
|
|
1038
|
+
// Close pane (NEW: using action system)
|
|
1039
|
+
actionSystem.executeAction(PaneAction.CLOSE, panes[selectedIndex]);
|
|
791
1040
|
}
|
|
792
1041
|
else if (key.return && selectedIndex < panes.length) {
|
|
793
|
-
|
|
1042
|
+
// Jump to pane (NEW: using action system)
|
|
1043
|
+
actionSystem.executeAction(PaneAction.VIEW, panes[selectedIndex]);
|
|
794
1044
|
}
|
|
795
1045
|
});
|
|
796
|
-
// If showing
|
|
797
|
-
if (
|
|
798
|
-
return (React.createElement(
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
setShowMergePane(false);
|
|
805
|
-
setMergingPane(null);
|
|
806
|
-
} }));
|
|
1046
|
+
// If showing QR code, render only that
|
|
1047
|
+
if (showQRCode && tunnelUrl) {
|
|
1048
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
1049
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
1050
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "dmux - Remote Access")),
|
|
1051
|
+
React.createElement(QRCode, { url: tunnelUrl }),
|
|
1052
|
+
React.createElement(Box, { marginTop: 1 },
|
|
1053
|
+
React.createElement(Text, { dimColor: true }, "Press ESC to return to pane list"))));
|
|
807
1054
|
}
|
|
808
1055
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
809
|
-
React.createElement(
|
|
810
|
-
React.createElement(Text, { bold: true, color: "cyan" },
|
|
811
|
-
"dmux - ",
|
|
812
|
-
projectName)),
|
|
813
|
-
React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, isLoading: isLoading, showNewPaneDialog: showNewPaneDialog, agentStatuses: agentStatuses }),
|
|
1056
|
+
React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, isLoading: isLoading, showNewPaneDialog: showNewPaneDialog, agentStatuses: agentStatuses, kebabMenuPaneIndex: kebabMenuPaneIndex ?? undefined }),
|
|
814
1057
|
isLoading && (React.createElement(LoadingIndicator, null)),
|
|
815
1058
|
showNewPaneDialog && !showAgentChoiceDialog && (React.createElement(NewPaneDialog, { value: newPanePrompt, onChange: setNewPanePrompt, onSubmit: (value) => {
|
|
816
1059
|
const promptValue = value;
|
|
@@ -835,15 +1078,28 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
835
1078
|
} })),
|
|
836
1079
|
showAgentChoiceDialog && (React.createElement(AgentChoiceDialog, { agentChoice: agentChoice })),
|
|
837
1080
|
isCreatingPane && (React.createElement(CreatingIndicator, { message: statusMessage })),
|
|
838
|
-
showMergeConfirmation && mergedPane && (React.createElement(MergeConfirmationDialog, { pane: mergedPane })),
|
|
839
|
-
showCloseOptions && closingPane && (React.createElement(CloseOptionsDialog, { pane: closingPane, selectedIndex: selectedCloseOption })),
|
|
840
1081
|
showCommandPrompt && (React.createElement(CommandPromptDialog, { type: showCommandPrompt, value: commandInput, onChange: setCommandInput })),
|
|
841
1082
|
showFileCopyPrompt && (React.createElement(FileCopyPrompt, null)),
|
|
1083
|
+
showKebabMenu && kebabMenuPaneIndex !== null && panes[kebabMenuPaneIndex] && (React.createElement(KebabMenu, { selectedOption: kebabMenuOption, actions: kebabMenuActions, paneName: panes[kebabMenuPaneIndex].slug })),
|
|
1084
|
+
actionSystem.actionState.showConfirmDialog && (React.createElement(ActionConfirmDialog, { key: "confirm-dialog", title: actionSystem.actionState.confirmTitle, message: actionSystem.actionState.confirmMessage, yesLabel: actionSystem.actionState.confirmYesLabel, noLabel: actionSystem.actionState.confirmNoLabel, selectedIndex: actionSystem.actionState.confirmSelectedIndex })),
|
|
1085
|
+
actionSystem.actionState.showChoiceDialog && (React.createElement(ActionChoiceDialog, { key: "choice-dialog", title: actionSystem.actionState.choiceTitle, message: actionSystem.actionState.choiceMessage, options: actionSystem.actionState.choiceOptions, selectedIndex: actionSystem.actionState.choiceSelectedIndex })),
|
|
1086
|
+
actionSystem.actionState.showInputDialog && (React.createElement(ActionInputDialog, { key: "input-dialog", title: actionSystem.actionState.inputTitle, message: actionSystem.actionState.inputMessage, placeholder: actionSystem.actionState.inputPlaceholder, value: actionSystem.actionState.inputValue, onValueChange: (value) => {
|
|
1087
|
+
actionSystem.setActionState(prev => ({ ...prev, inputValue: value }));
|
|
1088
|
+
} })),
|
|
1089
|
+
actionSystem.actionState.showProgressDialog && (React.createElement(ActionProgressDialog, { key: "progress-dialog", message: actionSystem.actionState.progressMessage, percent: actionSystem.actionState.progressPercent })),
|
|
842
1090
|
runningCommand && (React.createElement(RunningIndicator, null)),
|
|
843
1091
|
isUpdating && (React.createElement(UpdatingIndicator, null)),
|
|
1092
|
+
isCreatingTunnel && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, marginTop: 1 },
|
|
1093
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Creating tunnel..."),
|
|
1094
|
+
React.createElement(Box, { marginTop: 1 },
|
|
1095
|
+
React.createElement(Text, { dimColor: true }, "This may take a few moments...")))),
|
|
844
1096
|
statusMessage && (React.createElement(Box, { marginTop: 1 },
|
|
845
1097
|
React.createElement(Text, { color: "green" }, statusMessage))),
|
|
846
|
-
React.createElement(
|
|
1098
|
+
actionSystem.actionState.statusMessage && (React.createElement(Box, { marginTop: 1 },
|
|
1099
|
+
React.createElement(Text, { color: actionSystem.actionState.statusType === 'error' ? 'red' :
|
|
1100
|
+
actionSystem.actionState.statusType === 'success' ? 'green' :
|
|
1101
|
+
'cyan' }, actionSystem.actionState.statusMessage))),
|
|
1102
|
+
React.createElement(FooterHelp, { show: !showNewPaneDialog && !showCommandPrompt, showRemoteKey: !!server, quitConfirmMode: quitConfirmMode, gridInfo: (() => {
|
|
847
1103
|
if (!process.env.DEBUG_DMUX)
|
|
848
1104
|
return undefined;
|
|
849
1105
|
const cols = Math.max(1, Math.floor(terminalWidth / 37));
|
|
@@ -851,14 +1107,12 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
|
|
|
851
1107
|
const pos = getCardGridPosition(selectedIndex);
|
|
852
1108
|
return `Grid: ${cols} cols × ${rows} rows | Selected: row ${pos.row}, col ${pos.col} | Terminal: ${terminalWidth}w`;
|
|
853
1109
|
})() }),
|
|
854
|
-
React.createElement(
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
updateInfo.latestVersion,
|
|
861
|
-
" available! Run: npm i -g dmux@latest"))))));
|
|
1110
|
+
React.createElement(Text, { dimColor: true },
|
|
1111
|
+
"v",
|
|
1112
|
+
packageJson.version,
|
|
1113
|
+
updateAvailable && updateInfo && (React.createElement(Text, { color: "yellow" },
|
|
1114
|
+
" \u2022 Update available: ",
|
|
1115
|
+
updateInfo.latestVersion)))));
|
|
862
1116
|
};
|
|
863
1117
|
export default DmuxApp;
|
|
864
1118
|
//# sourceMappingURL=DmuxApp.js.map
|