|
|
@@ -5,7 +5,7 @@
|
|
|
import type { MultiplexerLayout } from '../../config/schema';
|
|
|
import { crossSpawn } from '../../utils/compat';
|
|
|
import { log } from '../../utils/logger';
|
|
|
-import { buildOpencodeAttachCommand, findBinary } from '../shared';
|
|
|
+import { buildOpencodeAttachCommand, findBinary, gracefulClosePane } from '../shared';
|
|
|
import type { Multiplexer, PaneResult } from '../types';
|
|
|
|
|
|
const TMUX_LAYOUT_DEBOUNCE_MS = 150;
|
|
|
@@ -113,54 +113,13 @@ export class TmuxMultiplexer implements Multiplexer {
|
|
|
}
|
|
|
|
|
|
async closePane(paneId: string): Promise<boolean> {
|
|
|
- if (!paneId) {
|
|
|
- log('[tmux] closePane: no paneId provided');
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
const tmux = await this.getBinary();
|
|
|
- if (!tmux) {
|
|
|
- log('[tmux] closePane: tmux binary not found');
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- // Send Ctrl+C for graceful shutdown
|
|
|
- log('[tmux] closePane: sending Ctrl+C', { paneId });
|
|
|
- const ctrlCProc = crossSpawn([tmux, 'send-keys', '-t', paneId, 'C-c'], {
|
|
|
- stdout: 'pipe',
|
|
|
- stderr: 'pipe',
|
|
|
- });
|
|
|
- await ctrlCProc.exited;
|
|
|
-
|
|
|
- // Wait for graceful shutdown
|
|
|
- await new Promise((r) => setTimeout(r, 250));
|
|
|
-
|
|
|
- // Kill the pane
|
|
|
- log('[tmux] closePane: killing pane', { paneId });
|
|
|
- const proc = crossSpawn([tmux, 'kill-pane', '-t', paneId], {
|
|
|
- stdout: 'pipe',
|
|
|
- stderr: 'pipe',
|
|
|
- });
|
|
|
-
|
|
|
- const exitCode = await proc.exited;
|
|
|
- const stderr = await proc.stderr();
|
|
|
-
|
|
|
- log('[tmux] closePane: result', { exitCode, stderr: stderr.trim() });
|
|
|
-
|
|
|
- if (exitCode === 0) {
|
|
|
- // Rebalance panes after bursts of child sessions settle.
|
|
|
- this.scheduleLayout();
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // Pane might already be closed
|
|
|
- log('[tmux] closePane: failed (pane may already be closed)', { paneId });
|
|
|
- return false;
|
|
|
- } catch (err) {
|
|
|
- log('[tmux] closePane: exception', { error: String(err) });
|
|
|
- return false;
|
|
|
- }
|
|
|
+ const closed = await gracefulClosePane(tmux, paneId, {
|
|
|
+ ctrlC: ['send-keys', '-t', paneId, 'C-c'],
|
|
|
+ close: ['kill-pane', '-t', paneId],
|
|
|
+ });
|
|
|
+ if (closed) this.scheduleLayout();
|
|
|
+ return closed;
|
|
|
}
|
|
|
|
|
|
async applyLayout(
|